diff --git a/.gitignore b/.gitignore index ee01dda..3f7e467 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ .idea/** +data/**/* + **/.DS_Store # Compiled class file @@ -138,4 +140,4 @@ gradle-app.setting # gradle/wrapper/gradle-wrapper.properties # IntelliJ -*.iml \ No newline at end of file +*.iml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ba82901..309580e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -62,4 +62,4 @@ code_quality: paths: - build/reports/cpd/cpdCheck.xml #create a report on the quality of the code - expose_as: 'Code Quality Report' \ No newline at end of file + expose_as: 'Code Quality Report' diff --git a/Dockerfile b/Dockerfile index 7a6dc45..c931d3a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,9 @@ FROM openjdk:13-jdk-alpine -ENV DB_URL "" -ENV DB_USER "" -ENV DB_PASS "" + ARG JAR_FILE=build/libs/smarthut*.jar + COPY ${JAR_FILE} app.jar + +ENV SMARTHUT_THIS_VALUE_IS_PROD_IF_THIS_IS_A_CONTAINER_PIZZOCCHERI=prod EXPOSE 8080 ENTRYPOINT ["java","-jar","/app.jar"] diff --git a/HELP.md b/HELP.md deleted file mode 100644 index 6b12689..0000000 --- a/HELP.md +++ /dev/null @@ -1,13 +0,0 @@ -# Getting Started - -### Reference Documentation -For further reference, please consider the following sections: - -* [Official Gradle documentation](https://docs.gradle.org) -* [Spring Boot Gradle Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.2.4.RELEASE/gradle-plugin/reference/html/) - -### Additional Links -These additional references should also help you: - -* [Gradle Build Scans – insights for your project's build](https://scans.gradle.com#gradle) - diff --git a/README.md b/README.md index d483ec4..f588721 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,53 @@ # backend +## Installation guide + +In order to install a SmartHut.sm, you can use *Docker* and *Docker Compose* +in order to create che corresponding containers. + +Use the following `docker-compose.yml` example file. Change the values +of `$PASSWORD` and `$SECRET` to respectively the chosen PostgreSQL password +and the JWT secret used to run the server. `$SECRET` must be at least 64 chars long. + +```yaml +version: '3' + +services: + smarthutdb: + restart: always + image: postgres:12-alpine + container_name: smarthutdb + volumes: + - ./data:/var/lib/postgresql/data + environment: + PGDATA: /var/lib/postgresql/data/data + POSTGRES_DB: smarthut + POSTGRES_USERNAME: postgres + POSTGRES_PASSWORD: $PASSWORD + + smarthutbackend: + restart: always + image: smarthutsm/smarthut-backend:M1 + ports: + - 8080:8080 + environment: + - POSTGRES_JDBC=jdbc:postgresql://smarthutdb:5432/smarthut + - POSTGRES_USER=postgres + - POSTGRES_PASS=$PASSWORD + - SECRET=$SECRET + - MAIL_HOST=smtp.gmail.com + - MAIL_PORT=587 + - MAIL_STARTTLS=true + - MAIL_USER=smarthut.sm@gmail.com + - MAIL_PASS=dcadvbagqfkwbfts + - BACKEND_URL=http://localhost:8080 + - FRONTEND_URL=http://localhost + + smarthut: + restart: always + image: smarthutsm/smarthut:M1 + ports: + - 80:80 + environment: + - BACKEND_URL=http://localhost:8080 +``` diff --git a/build.gradle b/build.gradle index 5140ea7..3116923 100644 --- a/build.gradle +++ b/build.gradle @@ -4,21 +4,16 @@ plugins { id "de.aaschmid.cpd" version "3.1" id 'java' } - group = 'ch.usi.inf.sa4.sanmarinoes' version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' - repositories { mavenCentral() } - dependencies { compile 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' compile "org.springframework.boot:spring-boot-starter-websocket" - implementation 'org.springframework.boot:spring-boot-starter' - implementation 'com.sun.mail:javax.mail:1.6.2' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-security' @@ -32,22 +27,18 @@ dependencies { compile 'io.springfox:springfox-swagger2:2.9.2' compile 'io.springfox:springfox-swagger-ui:2.9.2' compile 'org.springframework.boot:spring-boot-configuration-processor' - + testCompile 'org.springframework.boot:spring-boot-starter-webflux' implementation('org.springframework.boot:spring-boot-starter-web') { exclude group: 'org.springframework.boot', module: 'spring-boot-starter-json' } - testImplementation('org.springframework.boot:spring-boot-starter-test') { exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' } - testImplementation 'org.springframework.security:spring-security-test' testImplementation 'com.h2database:h2:1.4.200' - // Fixes https://stackoverflow.com/a/60455550 testImplementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.11' } - test { useJUnitPlatform() -} +} \ No newline at end of file diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/CORSFilter.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/CORSFilter.java index 7df826d..d5e19ae 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/CORSFilter.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/CORSFilter.java @@ -1,7 +1,6 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.config; import java.io.IOException; -import java.util.List; import javax.servlet.*; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; @@ -14,23 +13,13 @@ import org.springframework.stereotype.Component; @Component public class CORSFilter implements Filter { - static void setCORSHeaders(HttpServletResponse response) { + public static void setCORSHeaders(HttpServletResponse response) { response.setHeader("Access-Control-Allow-Origin", "*"); - response.setHeader("Access-Control-Allow-Methods", "HEAD, PUT, POST, GET, OPTIONS, DELETE"); - response.setHeader("Access-Control-Max-Age", "3600"); - response.setHeader( - "Access-Control-Allow-Headers", - String.join( - ",", - List.of( - "Access-Control-Allow-Headers", - "Origin", - "Accept", - "X-Requested-With", - "Authorization", - "Content-Type", - "Access-Control-Request-Method", - "Access-Control-Request-Headers"))); + response.setHeader("Access-Control-Allow-Methods", "*"); + response.setHeader("Access-Control-Allow-Headers", "*"); + response.setHeader("Access-Control-Allow-Credentials", "true"); + response.setHeader("Access-Control-Expose-Headers", "*"); + response.setHeader("Access-Control-Max-Age", "6".repeat(99)); } @Override diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/EmailConfigurationService.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/EmailConfigurationService.java index 8bae55d..a26aeeb 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/EmailConfigurationService.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/EmailConfigurationService.java @@ -30,17 +30,21 @@ public class EmailConfigurationService { */ @NotNull private String registrationPath; + /** + * The URL to follow for password reset email confirmation. Has to end with the start of a query + * parameter + */ + @NotNull private String resetPasswordPath; + /** The email subject for a reset password email */ @NotNull private String resetPasswordSubject; /** The text in the email body preceding the confirmation URL for a reset password email */ @NotNull private String resetPassword; - /** - * The URL to follow for password reset email confirmation. Has to end with the start of a query - * parameter - */ - @NotNull private String resetPasswordPath; + @NotNull private String resetPasswordRedirect; + + @NotNull private String registrationRedirect; public String getRegistrationSubject() { return registrationSubject; @@ -89,4 +93,20 @@ public class EmailConfigurationService { public void setResetPasswordPath(String resetPasswordPath) { this.resetPasswordPath = resetPasswordPath; } + + public String getResetPasswordRedirect() { + return resetPasswordRedirect; + } + + public void setResetPasswordRedirect(String resetPasswordRedirect) { + this.resetPasswordRedirect = resetPasswordRedirect; + } + + public String getRegistrationRedirect() { + return registrationRedirect; + } + + public void setRegistrationRedirect(String registrationRedirect) { + this.registrationRedirect = registrationRedirect; + } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/SpringFoxConfig.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/SpringFoxConfig.java index 2fdab4e..971a7fb 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/SpringFoxConfig.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/SpringFoxConfig.java @@ -1,6 +1,5 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.config; - import java.util.List; import java.util.function.Predicate; import org.springframework.context.annotation.Bean; @@ -75,7 +74,8 @@ public class SpringFoxConfig { .or(PathSelectors.regex("/sensor.*")::apply) .or(PathSelectors.regex("/smartPlug.*")::apply) .or(PathSelectors.regex("/switch.*")::apply) - .or(PathSelectors.regex("/motionSensor.*")::apply); + .or(PathSelectors.regex("/motionSensor.*")::apply) + .or(PathSelectors.regex("/auth/profile.*")::apply); } /** diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/AuthenticationController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/AuthenticationController.java index 3160e1c..ad48da2 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/AuthenticationController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/AuthenticationController.java @@ -3,11 +3,9 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; import ch.usi.inf.sa4.sanmarinoes.smarthut.config.JWTTokenUtils; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.JWTRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.JWTResponse; -import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserUpdateRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UnauthorizedException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UserNotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; -import io.swagger.annotations.Authorization; import java.security.Principal; import javax.validation.Valid; import org.springframework.security.authentication.AuthenticationManager; @@ -72,19 +70,9 @@ public class AuthenticationController { return new JWTResponse(token); } - @Authorization(value = "Bearer") - @PatchMapping("/update") - public User update( - @Valid @RequestBody final UserUpdateRequest userData, final Principal principal) { - final User oldUser = userRepository.findByUsername(principal.getName()); - if (userData.getName() != null) oldUser.setName(userData.getName()); - if (userData.getEmail() != null) { - oldUser.setEmail(userData.getEmail()); - // TODO: handle email verification - } - if (userData.getPassword() != null) - oldUser.setPassword(encoder.encode(userData.getPassword())); - return userRepository.save(oldUser); + @GetMapping("/profile") + public User profile(final Principal principal) { + return userRepository.findByUsername(principal.getName()); } private void authenticate(String username, String password) throws UnauthorizedException { diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/ButtonDimmerController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/ButtonDimmerController.java index 061c6b9..944bd8e 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/ButtonDimmerController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/ButtonDimmerController.java @@ -6,6 +6,7 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.ButtonDimmerDimRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.GenericDeviceSaveReguest; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; +import java.security.Principal; import java.util.List; import java.util.Set; import javax.validation.Valid; @@ -52,10 +53,13 @@ public class ButtonDimmerController } @PutMapping("/dim") - public Set dim(@Valid @RequestBody final ButtonDimmerDimRequest bd) + public Set dim( + @Valid @RequestBody final ButtonDimmerDimRequest bd, final Principal principal) throws NotFoundException { final ButtonDimmer buttonDimmer = - buttonDimmerRepository.findById(bd.getId()).orElseThrow(NotFoundException::new); + buttonDimmerRepository + .findByIdAndUsername(bd.getId(), principal.getName()) + .orElseThrow(NotFoundException::new); switch (bd.getDimType()) { case UP: diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DeviceController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DeviceController.java index 2d8dd99..17bdec7 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DeviceController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DeviceController.java @@ -27,16 +27,17 @@ public class DeviceController { } @PutMapping - public Device update(@Valid @RequestBody DeviceSaveRequest deviceSaveRequest) + public Device update( + @Valid @RequestBody DeviceSaveRequest deviceSaveRequest, final Principal principal) throws NotFoundException, BadDataException { final Device d = deviceRepository - .findById(deviceSaveRequest.getId()) + .findByIdAndUsername(deviceSaveRequest.getId(), principal.getName()) .orElseThrow(NotFoundException::new); // check if roomId is valid roomRepository - .findById(deviceSaveRequest.getRoomId()) + .findByIdAndUsername(deviceSaveRequest.getRoomId(), principal.getName()) .orElseThrow(() -> new BadDataException("roomId is not a valid room id")); d.setRoomId(deviceSaveRequest.getRoomId()); diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DimmableLightController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DimmableLightController.java index dd2e1e3..944b6c2 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DimmableLightController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DimmableLightController.java @@ -6,6 +6,7 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.DimmableLightSaveRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DimmableLight; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DimmableLightRepository; +import java.security.Principal; import java.util.List; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; @@ -43,10 +44,14 @@ public class DimmableLightController { } @PutMapping - public DimmableLight update(@Valid @RequestBody DimmableLightSaveRequest sp) + public DimmableLight update( + @Valid @RequestBody DimmableLightSaveRequest sp, final Principal principal) throws NotFoundException { return save( - dimmableLightService.findById(sp.getId()).orElseThrow(NotFoundException::new), sp); + dimmableLightService + .findByIdAndUsername(sp.getId(), principal.getName()) + .orElseThrow(NotFoundException::new), + sp); } @DeleteMapping("/{id}") diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/KnobDimmerController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/KnobDimmerController.java index 9f59889..c15d867 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/KnobDimmerController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/KnobDimmerController.java @@ -6,6 +6,7 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.GenericDeviceSaveReguest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.KnobDimmerDimRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; +import java.security.Principal; import java.util.List; import java.util.Set; import javax.validation.Valid; @@ -53,10 +54,13 @@ public class KnobDimmerController } @PutMapping("/dimTo") - public Set dimTo(@Valid @RequestBody final KnobDimmerDimRequest bd) + public Set dimTo( + @Valid @RequestBody final KnobDimmerDimRequest bd, final Principal principal) throws NotFoundException { final KnobDimmer dimmer = - knobDimmerRepository.findById(bd.getId()).orElseThrow(NotFoundException::new); + knobDimmerRepository + .findByIdAndUsername(bd.getId(), principal.getName()) + .orElseThrow(NotFoundException::new); dimmer.setLightIntensity(bd.getIntensity()); dimmableLightRepository.saveAll(dimmer.getOutputs()); diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/RegularLightController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/RegularLightController.java index 15ce2e8..e033555 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/RegularLightController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/RegularLightController.java @@ -6,6 +6,7 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.RegularLightSaveRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.RegularLight; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.RegularLightRepository; +import java.security.Principal; import java.util.List; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; @@ -50,10 +51,14 @@ public class RegularLightController { } @PutMapping - public RegularLight update(@Valid @RequestBody RegularLightSaveRequest rl) + public RegularLight update( + @Valid @RequestBody RegularLightSaveRequest rl, final Principal principal) throws NotFoundException { return save( - regularLightService.findById(rl.getId()).orElseThrow(NotFoundException::new), rl); + regularLightService + .findByIdAndUsername(rl.getId(), principal.getName()) + .orElseThrow(NotFoundException::new), + rl); } @DeleteMapping("/{id}") diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/RoomController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/RoomController.java index fb42037..2bfa440 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/RoomController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/RoomController.java @@ -23,6 +23,12 @@ public class RoomController { @Autowired private DeviceRepository deviceRepository; + @Autowired private SwitchRepository switchRepository; + + @Autowired private ButtonDimmerRepository buttonDimmerRepository; + + @Autowired private KnobDimmerRepository knobDimmerRepository; + @GetMapping public List findAll() { return toList(roomRepository.findAll()); @@ -33,44 +39,57 @@ public class RoomController { return roomRepository.findById(id).orElseThrow(NotFoundException::new); } - private Room save(final RoomSaveRequest r, final Principal principal, boolean setWhenNull) { - Room newRoom = new Room(); + @PostMapping + public @ResponseBody Room create( + @Valid @RequestBody RoomSaveRequest r, final Principal principal) { final String username = principal.getName(); final Long userId = userRepository.findByUsername(username).getId(); final String img = r.getImage(); final Room.Icon icon = r.getIcon(); + final Room newRoom = new Room(); newRoom.setUserId(userId); newRoom.setName(r.getName()); - if (img != null) { - newRoom.setImage(img); - } else if (setWhenNull) { - newRoom.setImage(null); + newRoom.setImage(img); + newRoom.setIcon(icon); + + return roomRepository.save(newRoom); + } + + @PutMapping("/{id}") + public @ResponseBody Room update( + @PathVariable("id") long id, @RequestBody RoomSaveRequest r, final Principal principal) + throws NotFoundException { + final Room newRoom = + roomRepository + .findByIdAndUsername(id, principal.getName()) + .orElseThrow(NotFoundException::new); + final String img = r.getImage(); + final Room.Icon icon = r.getIcon(); + + if (r.getName() != null) { + newRoom.setName(r.getName()); } + + if ("".equals(img)) { + newRoom.setImage(null); + } else if (img != null) { + newRoom.setImage(img); + } + if (icon != null) { newRoom.setIcon(icon); - } else if (setWhenNull) { - newRoom.setIcon(null); } return roomRepository.save(newRoom); } - @PostMapping - public @ResponseBody Room create( - @Valid @RequestBody RoomSaveRequest r, final Principal principal) { - return this.save(r, principal, true); - } - - @PutMapping - public @ResponseBody Room update( - @Valid @RequestBody RoomSaveRequest r, final Principal principal) { - return this.save(r, principal, false); - } - @DeleteMapping("/{id}") public void deleteById(@PathVariable("id") long id) { + switchRepository.deleteAllByRoomId(id); + knobDimmerRepository.deleteAllByRoomId(id); + buttonDimmerRepository.deleteAllByRoomId(id); roomRepository.deleteById(id); } @@ -79,7 +98,7 @@ public class RoomController { * id). */ @GetMapping(path = "/{roomId}/devices") - public @ResponseBody List getDevices(@PathVariable("roomId") long roomid) { + public List getDevices(@PathVariable("roomId") long roomid) { return deviceRepository.findByRoomId(roomid); } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SmartPlugController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SmartPlugController.java index 8a57430..5ef4eed 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SmartPlugController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SmartPlugController.java @@ -5,6 +5,7 @@ import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SmartPlugSaveRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; +import java.security.Principal; import java.util.*; import java.util.List; import javax.validation.Valid; @@ -14,7 +15,7 @@ import org.springframework.web.bind.annotation.*; @RestController @EnableAutoConfiguration -@RequestMapping("/smartplug") +@RequestMapping("/smartPlug") public class SmartPlugController { @Autowired private SmartPlugRepository smartPlugRepository; @@ -44,9 +45,25 @@ public class SmartPlugController { } @PutMapping - public SmartPlug update(@Valid @RequestBody SmartPlugSaveRequest sp) throws NotFoundException { + public SmartPlug update(@Valid @RequestBody SmartPlugSaveRequest sp, final Principal principal) + throws NotFoundException { return save( - smartPlugRepository.findById(sp.getId()).orElseThrow(NotFoundException::new), sp); + smartPlugRepository + .findByIdAndUsername(sp.getId(), principal.getName()) + .orElseThrow(NotFoundException::new), + sp); + } + + @DeleteMapping("/{id}/meter") + public SmartPlug resetMeter(@PathVariable("id") long id, final Principal principal) + throws NotFoundException { + final SmartPlug s = + smartPlugRepository + .findByIdAndUsername(id, principal.getName()) + .orElseThrow(NotFoundException::new); + + s.resetTotalConsumption(); + return smartPlugRepository.save(s); } @DeleteMapping("/{id}") diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SwitchController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SwitchController.java index f90108c..fc64ccb 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SwitchController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SwitchController.java @@ -6,6 +6,7 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.GenericDeviceSaveReguest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SwitchOperationRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; +import java.security.Principal; import java.util.*; import java.util.List; import javax.validation.Valid; @@ -55,9 +56,13 @@ public class SwitchController extends InputDeviceConnectionController operate(@Valid @RequestBody final SwitchOperationRequest sr) + public Set operate( + @Valid @RequestBody final SwitchOperationRequest sr, final Principal principal) throws NotFoundException { - final Switch s = switchRepository.findById(sr.getId()).orElseThrow(NotFoundException::new); + final Switch s = + switchRepository + .findByIdAndUsername(sr.getId(), principal.getName()) + .orElseThrow(NotFoundException::new); switch (sr.getType()) { case ON: diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/UserAccountController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/UserAccountController.java index ebf354f..950fb1a 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/UserAccountController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/UserAccountController.java @@ -13,6 +13,8 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationTokenRepository; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository; import ch.usi.inf.sa4.sanmarinoes.smarthut.service.EmailSenderService; +import java.io.IOException; +import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import javax.validation.constraints.NotNull; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -158,8 +160,10 @@ public class UserAccountController { * @throws EmailTokenNotFoundException if given token is not a valid token for password reset */ @PutMapping("/reset-password") - public OkResponse resetPassword(@Valid @RequestBody PasswordResetRequest resetRequest) - throws EmailTokenNotFoundException { + public OkResponse resetPassword( + @Valid @RequestBody PasswordResetRequest resetRequest, + final HttpServletResponse response) + throws EmailTokenNotFoundException, IOException { final ConfirmationToken token = confirmationTokenRepository.findByConfirmationToken( resetRequest.getConfirmationToken()); @@ -187,16 +191,17 @@ public class UserAccountController { * confirmation */ @GetMapping(value = "/confirm-account") - public OkResponse confirmUserAccount(@RequestParam("token") @NotNull String confirmationToken) - throws EmailTokenNotFoundException { + public void confirmUserAccount( + @RequestParam("token") @NotNull String confirmationToken, + final HttpServletResponse response) + throws EmailTokenNotFoundException, IOException { final ConfirmationToken token = confirmationTokenRepository.findByConfirmationToken(confirmationToken); if (token != null && !token.getResetPassword()) { token.getUser().setEnabled(true); userRepository.save(token.getUser()); - // TODO: redirect to frontend - return new OkResponse(); + response.sendRedirect(emailConfig.getRegistrationRedirect()); } else { throw new EmailTokenNotFoundException(); } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/DimmableLightSaveRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/DimmableLightSaveRequest.java index 01bec1a..74e911b 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/DimmableLightSaveRequest.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/DimmableLightSaveRequest.java @@ -11,7 +11,7 @@ public class DimmableLightSaveRequest { /** The light intensity value. Goes from 0 (off) to 100 (on) */ @NotNull - @Min(1) + @Min(0) @Max(100) private Integer intensity = 0; diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/RoomSaveRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/RoomSaveRequest.java index ca8fa0f..02a0e35 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/RoomSaveRequest.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/RoomSaveRequest.java @@ -6,6 +6,9 @@ import javax.validation.constraints.NotNull; public class RoomSaveRequest { + /** Room identifier */ + private long id; + @NotNull private Room.Icon icon; /** @@ -19,6 +22,14 @@ public class RoomSaveRequest { /** The user given name of this room (e.g. 'Master bedroom') */ @NotNull private String name; + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + public String getName() { return name; } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ButtonDimmer.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ButtonDimmer.java index fc91c22..da9af1c 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ButtonDimmer.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ButtonDimmer.java @@ -13,7 +13,7 @@ public class ButtonDimmer extends Dimmer { private static final int DIM_INCREMENT = 10; public ButtonDimmer() { - super("button-dimmer"); + super("buttonDimmer"); } /** Increases the current intensity level of the dimmable light by DIM_INCREMENT */ diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ButtonDimmerRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ButtonDimmerRepository.java index 484977a..f39644a 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ButtonDimmerRepository.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ButtonDimmerRepository.java @@ -1,3 +1,9 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; -public interface ButtonDimmerRepository extends DeviceRepository {} +import javax.transaction.Transactional; + +public interface ButtonDimmerRepository extends DeviceRepository { + + @Transactional + void deleteAllByRoomId(long roomId); +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ConfirmationTokenRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ConfirmationTokenRepository.java index 9bf3791..40c6a17 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ConfirmationTokenRepository.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ConfirmationTokenRepository.java @@ -6,6 +6,8 @@ import org.springframework.data.repository.CrudRepository; public interface ConfirmationTokenRepository extends CrudRepository { ConfirmationToken findByConfirmationToken(String confirmationToken); + ConfirmationToken findByUser(User user); + @Transactional void deleteByUserAndResetPassword(User user, boolean resetPassword); } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java index c564914..295fe37 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java @@ -36,7 +36,7 @@ public abstract class Device { * The room this device belongs in, as a foreign key id. To use when updating and inserting from * a REST call. */ - @Column(name = "room_id", nullable = false, unique = true) + @Column(name = "room_id", nullable = false) @NotNull private Long roomId; @@ -49,17 +49,13 @@ public abstract class Device { * The name for the category of this particular device (e.g 'dimmer'). Not stored in the * database but set thanks to constructors */ - @ApiModelProperty(hidden = true) - @Transient - private final String kind; + @Transient private final String kind; /** * The way this device behaves in the automation flow. Not stored in the database but set thanks * to constructors */ - @ApiModelProperty(hidden = true) - @Transient - private final FlowType flowType; + @Transient private final FlowType flowType; public long getId() { return id; diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DeviceRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DeviceRepository.java index f844882..2496029 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DeviceRepository.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DeviceRepository.java @@ -2,6 +2,7 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; import java.util.List; import java.util.Optional; +import javax.transaction.Transactional; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; @@ -20,6 +21,7 @@ public interface DeviceRepository extends CrudRepository findByIdAndUsername(Long id, String username); @@ -29,6 +31,7 @@ public interface DeviceRepository extends CrudRepository findAllByUsername(String username); @@ -38,6 +41,7 @@ public interface DeviceRepository extends CrudRepository 100) { this.intensity = 100; + this.oldIntensity = 100; } else { this.intensity = intensity; + this.oldIntensity = intensity; } } @@ -67,13 +73,13 @@ public class DimmableLight extends Switchable { @Override public void setOn(boolean on) { - intensity = on ? 100 : 0; + intensity = on ? oldIntensity : 0; } public void setDimmerId(Long dimmerId) { this.dimmerId = dimmerId; super.setSwitchId(null); - }; + } @Override public void setSwitchId(Long switchId) { diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Dimmer.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Dimmer.java index e192015..d06bece 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Dimmer.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Dimmer.java @@ -6,6 +6,7 @@ import javax.persistence.Entity; import javax.persistence.Inheritance; import javax.persistence.InheritanceType; import javax.persistence.OneToMany; +import javax.persistence.PreRemove; /** Represents a generic dimmer input device */ @Entity @@ -32,4 +33,11 @@ public abstract class Dimmer extends InputDevice { public void addDimmableLight(DimmableLight dimmableLight) { lights.add(dimmableLight); } + + @PreRemove + private void removeLightsFromDimmer() { + for (DimmableLight dl : getOutputs()) { + dl.setDimmerId(null); + } + } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/KnobDimmer.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/KnobDimmer.java index ce3745c..c116165 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/KnobDimmer.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/KnobDimmer.java @@ -10,7 +10,7 @@ import javax.persistence.Entity; public class KnobDimmer extends Dimmer { public KnobDimmer() { - super("knob-dimmer"); + super("knobDimmer"); } /** diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/KnobDimmerRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/KnobDimmerRepository.java index 7d7fc16..2a4558e 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/KnobDimmerRepository.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/KnobDimmerRepository.java @@ -1,3 +1,9 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; -public interface KnobDimmerRepository extends DeviceRepository {} +import javax.transaction.Transactional; + +public interface KnobDimmerRepository extends DeviceRepository { + + @Transactional + void deleteAllByRoomId(long roomId); +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/MotionSensor.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/MotionSensor.java index 50d2206..6c5baf7 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/MotionSensor.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/MotionSensor.java @@ -19,6 +19,6 @@ public class MotionSensor extends InputDevice { } public MotionSensor() { - super("motion-sensor"); + super("motionSensor"); } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RegularLight.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RegularLight.java index de9c10a..2dcff1b 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RegularLight.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RegularLight.java @@ -14,7 +14,7 @@ public class RegularLight extends Switchable { boolean on; public RegularLight() { - super("regular-light"); + super("regularLight"); this.on = false; } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Room.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Room.java index 68c3fc0..34f3824 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Room.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Room.java @@ -3,6 +3,8 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude; import com.google.gson.annotations.SerializedName; import io.swagger.annotations.ApiModelProperty; +import java.util.HashSet; +import java.util.Set; import javax.persistence.*; import javax.validation.constraints.NotNull; @@ -125,7 +127,6 @@ public class Room { * https://www.baeldung.com/java-base64-image-string * https://docs.oracle.com/javase/8/docs/api/java/util/Base64.html */ - @Lob @Column(name = "image", columnDefinition = "TEXT") private String image; @@ -134,12 +135,16 @@ public class Room { @GsonExclude private User user; + @OneToMany(mappedBy = "room", orphanRemoval = true) + @GsonExclude + private Set devices = new HashSet<>(); + /** * User that owns the house this room is in as a foreign key id. To use when updating and * inserting from a REST call. */ @NotNull - @Column(name = "user_id", nullable = false, unique = true) + @Column(name = "user_id", nullable = false) private Long userId; /** The user given name of this room (e.g. 'Master bedroom') */ @@ -187,6 +192,10 @@ public class Room { this.image = image; } + public Set getDevices() { + return devices; + } + @Override public String toString() { return "Room{" + "id=" + id + ", name='" + name + "\'}"; diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RoomRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RoomRepository.java index 08b4298..b02413d 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RoomRepository.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RoomRepository.java @@ -1,5 +1,18 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; +import java.util.Optional; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; -public interface RoomRepository extends CrudRepository {} +public interface RoomRepository extends CrudRepository { + + /** + * Finds a room by their id and a username + * + * @param id the room id + * @param username a User's username + * @return an optional device, empty if none found + */ + @Query("SELECT r FROM Room r JOIN r.user u WHERE r.id = ?1 AND u.username = ?2") + Optional findByIdAndUsername(Long id, String username); +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Sensor.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Sensor.java index 525ceb3..6b5e6b9 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Sensor.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Sensor.java @@ -35,7 +35,7 @@ public class Sensor extends InputDevice { } /** The value of this sensor according to its sensor type */ - @Column(nullable = false, length = 10, precision = 1) + @Column(nullable = false, precision = 11, scale = 1) private BigDecimal value; /** The type of this sensor */ diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SmartPlug.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SmartPlug.java index fe936b3..9b07e69 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SmartPlug.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SmartPlug.java @@ -1,5 +1,6 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; +import java.math.BigDecimal; import javax.persistence.Column; import javax.persistence.Entity; import javax.validation.constraints.NotNull; @@ -8,11 +9,28 @@ import javax.validation.constraints.NotNull; @Entity public class SmartPlug extends Switchable { + /** The average consumption of an active plug when on in Watt */ + public static final Double AVERAGE_CONSUMPTION_KW = 200.0; + + /** The total amount of power that the smart plug has consumed represented in W/h */ + @Column(precision = 13, scale = 3) + @NotNull + private BigDecimal totalConsumption = BigDecimal.ZERO; + /** Whether the smart plug is on */ @Column(name = "smart_plug_on", nullable = false) @NotNull private boolean on; + public BigDecimal getTotalConsumption() { + return totalConsumption; + } + + /** Resets the consuption meter */ + public void resetTotalConsumption() { + totalConsumption = BigDecimal.ZERO; + } + @Override public boolean isOn() { return on; @@ -24,6 +42,6 @@ public class SmartPlug extends Switchable { } public SmartPlug() { - super("smart-plug"); + super("smartPlug"); } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SmartPlugRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SmartPlugRepository.java index 08d145d..8a02243 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SmartPlugRepository.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SmartPlugRepository.java @@ -1,3 +1,24 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; -public interface SmartPlugRepository extends SwitchableRepository {} +import java.util.Collection; +import javax.transaction.Transactional; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; + +public interface SmartPlugRepository extends SwitchableRepository { + @Transactional + Collection findByOn(boolean on); + + /** + * Updates total consumption of all activated smart plugs by considering a load of + * fakeConsumption W. This query must be executed every second + * + * @see ch.usi.inf.sa4.sanmarinoes.smarthut.scheduled.UpdateTasks + * @param fakeConsumption the fake consumption in watts + */ + @Modifying(clearAutomatically = true) + @Transactional + @Query( + "UPDATE SmartPlug s SET totalConsumption = s.totalConsumption + ?1 / 3600.0 WHERE s.on = true") + void updateTotalConsumption(Double fakeConsumption); +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Switch.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Switch.java index d819dfe..8a8057c 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Switch.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Switch.java @@ -5,6 +5,7 @@ import java.util.Set; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.OneToMany; +import javax.persistence.PreRemove; /** A switch input device */ @Entity @@ -51,4 +52,11 @@ public class Switch extends InputDevice { public Set getOutputs() { return switchables; } + + @PreRemove + public void removeSwitchable() { + for (Switchable s : getOutputs()) { + s.setSwitchId(null); + } + } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SwitchRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SwitchRepository.java index f06f465..8650ebe 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SwitchRepository.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SwitchRepository.java @@ -1,3 +1,9 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; -public interface SwitchRepository extends DeviceRepository {} +import javax.transaction.Transactional; + +public interface SwitchRepository extends DeviceRepository { + + @Transactional + void deleteAllByRoomId(long roomId); +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java index dc6766d..60aad17 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java @@ -1,5 +1,6 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; +import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude; import io.swagger.annotations.ApiModelProperty; import java.util.Objects; import javax.persistence.*; @@ -24,6 +25,7 @@ public class User { /** A properly salted way to store the password */ @Column(nullable = false) + @GsonExclude private String password; /** @@ -34,7 +36,7 @@ public class User { private String email; @Column(nullable = false) - @ApiModelProperty(hidden = true) + @GsonExclude private Boolean isEnabled = false; public Long getId() { diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/scheduled/SensorUpdateTasks.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/scheduled/UpdateTasks.java similarity index 74% rename from src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/scheduled/SensorUpdateTasks.java rename to src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/scheduled/UpdateTasks.java index b1614aa..932db5c 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/scheduled/SensorUpdateTasks.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/scheduled/UpdateTasks.java @@ -2,10 +2,10 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.scheduled; import ch.usi.inf.sa4.sanmarinoes.smarthut.controller.MotionSensorController; import ch.usi.inf.sa4.sanmarinoes.smarthut.controller.SensorController; -import ch.usi.inf.sa4.sanmarinoes.smarthut.models.MotionSensorRepository; -import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Sensor; -import ch.usi.inf.sa4.sanmarinoes.smarthut.models.SensorRepository; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; +import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint; import java.math.BigDecimal; +import java.util.Collection; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.stream.StreamSupport; @@ -13,18 +13,25 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; -/** Generates fake sensor (and motion sensor) updates as required by milestone one */ +/** + * Generates fake sensor (and motion sensor) and smart plug consumption updates as required by + * milestone one + */ @Component -public class SensorUpdateTasks { +public class UpdateTasks { @Autowired private SensorRepository sensorRepository; @Autowired private MotionSensorRepository motionSensorRepository; + @Autowired private SmartPlugRepository smartPlugRepository; + @Autowired private SensorController sensorController; @Autowired private MotionSensorController motionSensorController; + @Autowired private SensorSocketEndpoint sensorSocketEndpoint; + /** Generates fake sensor updates every two seconds with a +/- 1.25% error */ @Scheduled(fixedRate = 2000) public void sensorFakeUpdate() { @@ -59,4 +66,12 @@ public class SensorUpdateTasks { sensor, false)); }); } + + /** Updates power consumption of all activated smart plugs every second */ + @Scheduled(fixedDelay = 1000) + public void smartPlugConsumptionFakeUpdate() { + smartPlugRepository.updateTotalConsumption(SmartPlug.AVERAGE_CONSUMPTION_KW); + final Collection c = smartPlugRepository.findByOn(true); + c.forEach(s -> sensorSocketEndpoint.broadcast(s, sensorRepository.findUser(s.getId()))); + } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/SensorSocketEndpoint.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/SensorSocketEndpoint.java index bc2f90e..6ff63b2 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/SensorSocketEndpoint.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/SensorSocketEndpoint.java @@ -1,13 +1,12 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.socket; -import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.didThrow; - import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonConfig; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import com.google.gson.Gson; +import java.io.IOException; import java.util.*; import javax.websocket.*; import org.springframework.beans.factory.annotation.Autowired; @@ -57,12 +56,19 @@ public class SensorSocketEndpoint extends Endpoint { * @param u the user to which to send the message * @return number of successful transfer */ - public long broadcast(Object message, User u) { - final Collection sessions = authorizedClients.get(u); - return sessions.stream() - .parallel() - .filter(didThrow(s -> s.getBasicRemote().sendText(gson.toJson(message)))) - .count(); + public void broadcast(Object message, User u) { + final HashSet sessions = new HashSet<>(authorizedClients.get(u)); + for (Session s : sessions) { + try { + if (s.isOpen()) { + s.getBasicRemote().sendText(gson.toJson(message)); + } else { + authorizedClients.remove(u, s); + } + } catch (IOException e) { + e.printStackTrace(); + } + } } /** diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties new file mode 100644 index 0000000..3c77362 --- /dev/null +++ b/src/main/resources/application-dev.properties @@ -0,0 +1,35 @@ +spring.http.converters.preferred-json-mapper=gson +spring.datasource.url=jdbc:postgresql://localhost:5432/smarthut +spring.datasource.username=postgres +spring.datasource.password= + +# Hibernate properties +spring.jpa.database=POSTGRESQL +spring.jpa.show-sql=false +spring.jpa.hibernate.ddl-auto=update +spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl +spring.jpa.properties.hibernate.format_sql=true + +jwt.secret=thiskeymustbeverylongorthethingcomplainssoiamjustgoingtowritehereabunchofgarbageciaomamma + +spring.mail.test-connection=true +spring.mail.host=smtp.gmail.com +spring.mail.port=587 +spring.mail.properties.mail.smtp.starttls.enable=true +spring.mail.username=smarthut.sm@gmail.com +spring.mail.password=dcadvbagqfkwbfts +spring.mail.properties.mail.smtp.starttls.required=true +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.connectiontimeout=5000 +spring.mail.properties.mail.smtp.timeout=5000 +spring.mail.properties.mail.smtp.writetimeout=5000 + +email.registrationSubject=Complete your SmartHut.sm registration +email.registration=To confirm your registration, please click here: +email.registrationPath=http://localhost:8080/register/confirm-account?token= +email.registrationRedirect=http://localhost:3000 + +email.resetpasswordSubject=SmartHut.sm password reset +email.resetpassword=To reset your password, please click here: +email.resetpasswordPath=http://localhost:3000/password-reset?token= +email.resetPasswordRedirect=http://localhost:3000/conf-reset-pass \ No newline at end of file diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties new file mode 100644 index 0000000..482fa13 --- /dev/null +++ b/src/main/resources/application-prod.properties @@ -0,0 +1,42 @@ +spring.http.converters.preferred-json-mapper=gson + +# Database connection properties +spring.datasource.url=${POSTGRES_JDBC} +spring.datasource.username=${POSTGRES_USER} +spring.datasource.password=${POSTGRES_PASS} + +# Hibernate properties +spring.jpa.database=POSTGRESQL +spring.jpa.show-sql=false +spring.jpa.hibernate.ddl-auto=update +spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl +spring.jpa.properties.hibernate.format_sql=true + +# JWT secret +jwt.secret=${SECRET} + +# Mail connection properties +spring.mail.test-connection=true +spring.mail.host=${MAIL_HOST} +spring.mail.port=${MAIL_PORT} +spring.mail.properties.mail.smtp.starttls.enable=${MAIL_STARTTLS} +spring.mail.username=${MAIL_USER} +spring.mail.password=${MAIL_PASS} +spring.mail.properties.mail.smtp.starttls.required=${MAIL_STARTTLS} +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.connectiontimeout=5000 +spring.mail.properties.mail.smtp.timeout=5000 +spring.mail.properties.mail.smtp.writetimeout=5000 + +# Registration email properties +email.registrationSubject=Complete your SmartHut.sm registration +email.registration=To confirm your registration, please click here: +email.registrationPath=${BACKEND_URL}/register/confirm-account?token= +email.registrationSuccess=${FRONTEND_URL} + + +# Password reset email properties +email.resetpasswordSubject=SmartHut.sm password reset +email.resetpassword=To reset your password, please click here: +email.resetpasswordPath=${FRONTEND_URL}/password-reset?token= +email.resetPasswordSuccess=${FRONTEND_URL}/conf-reset-pass \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f760ec8..2150e4d 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,33 +1 @@ -spring.http.converters.preferred-json-mapper=gson -spring.datasource.url=jdbc:postgresql://localhost:5432/smarthut -spring.datasource.username=postgres -spring.datasource.password= - -# Hibernate properties -spring.jpa.database=POSTGRESQL -spring.jpa.show-sql=true -spring.jpa.hibernate.ddl-auto=update -spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl -spring.jpa.properties.hibernate.format_sql=true - -jwt.secret=thiskeymustbeverylongorthethingcomplainssoiamjustgoingtowritehereabunchofgarbageciaomamma - -spring.mail.test-connection=true -spring.mail.host=smtp.gmail.com -spring.mail.port=587 -spring.mail.properties.mail.smtp.starttls.enable=true -spring.mail.username=smarthut.sm@gmail.com -spring.mail.password=dcadvbagqfkwbfts -spring.mail.properties.mail.smtp.starttls.required=true -spring.mail.properties.mail.smtp.auth=true -spring.mail.properties.mail.smtp.connectiontimeout=5000 -spring.mail.properties.mail.smtp.timeout=5000 -spring.mail.properties.mail.smtp.writetimeout=5000 - -email.registrationSubject=Complete your SmartHut.sm registration -email.registration=To confirm your registration, please click here: -email.registrationPath=http://localhost:8080/register/confirm-account?token= - -email.resetpasswordSubject=SmartHut.sm password reset -email.resetpassword=To reset your password, please click here: -email.resetpasswordPath=http://localhost:3000/password-reset?token= \ No newline at end of file +spring.profiles.active=${SMARTHUT_THIS_VALUE_IS_PROD_IF_THIS_IS_A_CONTAINER_PIZZOCCHERI:dev} \ No newline at end of file diff --git a/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/AuthenticationTests.java b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/AuthenticationTests.java index 60761cd..d13104f 100644 --- a/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/AuthenticationTests.java +++ b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/AuthenticationTests.java @@ -8,6 +8,8 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.OkResponse; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserRegistrationRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateRegistrationException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UnauthorizedException; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationTokenRepository; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import java.util.Map; @@ -25,6 +27,10 @@ public class AuthenticationTests extends SmartHutTest { @Autowired private TestRestTemplate restTemplate; + @Autowired private UserRepository userRepository; + + @Autowired private ConfirmationTokenRepository tokenRepository; + private UserRegistrationRequest getDisabledUser() { final UserRegistrationRequest disabledUser = new UserRegistrationRequest(); disabledUser.setName("Disabled User"); @@ -34,15 +40,6 @@ public class AuthenticationTests extends SmartHutTest { return disabledUser; } - private static final UserRegistrationRequest enabledUser = new UserRegistrationRequest(); - - static { - enabledUser.setName("Enabled User"); - enabledUser.setEmail("enabled@example.com"); - enabledUser.setUsername("enabled"); - enabledUser.setPassword("password"); - } - @Override protected void setUp() { final ResponseEntity res = @@ -50,12 +47,7 @@ public class AuthenticationTests extends SmartHutTest { this.url("/register"), getDisabledUser(), OkResponse.class); assertThat(res.getStatusCode().equals(HttpStatus.OK)); - final ResponseEntity res2 = - this.restTemplate.postForEntity( - this.url("/register"), enabledUser, OkResponse.class); - assertThat(res2.getStatusCode().equals(HttpStatus.OK)); - - // TODO: email confirmation for enabledUser + registerTestUser(restTemplate, userRepository, tokenRepository); } @Test @@ -230,4 +222,18 @@ public class AuthenticationTests extends SmartHutTest { assertThat(res.getBody() != null); assertThat(res.getBody().isUserDisabled()); } + + @Test + public void loginShouldReturnTokenWithEnabledUser() { + final JWTRequest request = new JWTRequest(); + request.setUsernameOrEmail("enabled"); + request.setPassword("password"); + + final ResponseEntity res = + this.restTemplate.postForEntity(url("/auth/login"), request, JWTResponse.class); + assertThat(res.getStatusCode().equals(HttpStatus.OK)); + assertThat(res.getBody() != null); + assertThat(res.getBody().getToken() != null); + assertThat(!res.getBody().getToken().isEmpty()); + } } diff --git a/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/KnobDimmerTests.java b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/KnobDimmerTests.java new file mode 100644 index 0000000..155242d --- /dev/null +++ b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/KnobDimmerTests.java @@ -0,0 +1,46 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut; + +import static org.junit.jupiter.api.Assertions.*; + +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DimmableLight; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.KnobDimmer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@DisplayName("A Knob Dimmer") +public class KnobDimmerTests { + + KnobDimmer knobDimmer; + + @BeforeEach + public void createNewKnobDimmer() { + this.knobDimmer = new KnobDimmer(); + } + + @Nested + @DisplayName(" when multiple lights are present") + class MultipleLights { + + @BeforeEach + public void setLights() { + DimmableLight dl; + for (int i = 0; i < 3; i++) { + dl = new DimmableLight(); + dl.setIntensity(10); + ; + knobDimmer.addDimmableLight(dl); + } + } + + @Test + @DisplayName(" set the intensity ") + public void increase() { + knobDimmer.setLightIntensity(30); + for (DimmableLight dl : knobDimmer.getOutputs()) { + assertEquals(30, dl.getIntensity()); + } + } + } +} diff --git a/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmartHutTest.java b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmartHutTest.java index 5c6e097..f2b737a 100644 --- a/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmartHutTest.java +++ b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmartHutTest.java @@ -1,6 +1,18 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut; +import static org.assertj.core.api.Assertions.assertThat; + +import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.OkResponse; +import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserRegistrationRequest; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationToken; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationTokenRepository; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository; import org.junit.jupiter.api.BeforeEach; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.reactive.function.client.WebClient; public abstract class SmartHutTest { private boolean setupDone = false; @@ -15,6 +27,38 @@ public abstract class SmartHutTest { protected void setUp() {} + protected static final UserRegistrationRequest enabledUser = new UserRegistrationRequest(); + + static { + enabledUser.setName("Enabled User"); + enabledUser.setEmail("enabled@example.com"); + enabledUser.setUsername("enabled"); + enabledUser.setPassword("password"); + } + + protected void registerTestUser( + final TestRestTemplate restTemplate, + final UserRepository userRepository, + final ConfirmationTokenRepository tokenRepository) { + final ResponseEntity res2 = + restTemplate.postForEntity(this.url("/register"), enabledUser, OkResponse.class); + assertThat(res2.getStatusCode().equals(HttpStatus.OK)); + + final User persistedEnabledUser = userRepository.findByUsername("enabled"); + final ConfirmationToken token = tokenRepository.findByUser(persistedEnabledUser); + + final ResponseEntity res3 = + WebClient.create(getBaseURL()) + .get() + .uri("/register/confirm-account?token=" + token.getConfirmationToken()) + .retrieve() + .toBodilessEntity() + .block(); + + assertThat(res3.getStatusCode().is2xxSuccessful()); + assertThat(userRepository.findByUsername("enabled").getEnabled()); + } + @BeforeEach void setUpHack() { if (!setupDone) { diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index ce6fe39..bdaafc0 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -29,7 +29,9 @@ server.port = 2000 email.registrationSubject=Complete your SmartHut.sm registration email.registration=To confirm your registration, please click here: email.registrationPath=http://localhost:8080/register/confirm-account?token= +email.registrationRedirect=http://localhost:3000 email.resetpasswordSubject=SmartHut.sm password reset email.resetpassword=To reset your password, please click here: -email.resetpasswordPath=http://localhost:3000/password-reset?token= \ No newline at end of file +email.resetpasswordPath=http://localhost:3000/password-reset?token= +email.resetPasswordRedirect=http://localhost:3000/conf-reset-pass \ No newline at end of file