diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f73ad4c..fc62202 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,17 +1,61 @@ -code_quality: - image: docker:stable - variables: - DOCKER_DRIVER: overlay2 - allow_failure: true +#Trying to set up the CI, probably won't work +image: gradle:jdk13 + +stages: + - build + - test + - code_quality + - deploy + +#Sets up the docker +smarthut_deploy: + stage: deploy + image: docker:latest services: - - docker:stable-dind + - docker:dind + variables: + DOCKER_DRIVER: overlay + before_script: + - docker version + - docker info + - docker login -u smarthutsm -p $CI_DOCKER_PASS #GiovanniRoberto script: - - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/') - - docker run - --env SOURCE_CODE="$PWD" - --volume "$PWD":/code - --volume /var/run/docker.sock:/var/run/docker.sock - "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code + - "docker build -t smarthutsm/smarthut:${CI_COMMIT_BRANCH} --pull ." + - "docker push smarthutsm/smarthut:${CI_COMMIT_BRANCH}" + after_script: + - docker logout + + +#base checks for the code +build: + stage: build + script: + - gradle clean + - gradle assemble artifacts: + paths: + - build/libs/*.jar + expire_in: 1 week + +#Runs the various tests and creates a report on the test coverage +test: + stage: test + script: + - gradle test + artifacts: + paths: + - build/test-results/test/TEST-*.xml reports: - codequality: gl-code-quality-report.json + junit: build/test-results/test/TEST-*.xml + +#Runs a quality check on the code and creates a report on the codes +code_quality: + stage: code_quality + script: + - gradle cpdCheck + artifacts: + paths: + - build/reports/cpd/cpdCheck.xml + #create a report on the quality of the code + expose_as: 'Code Quality Report' + allow_failure: true diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7a6dc45 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +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 +EXPOSE 8080 +ENTRYPOINT ["java","-jar","/app.jar"] diff --git a/build.gradle b/build.gradle index c7b20f6..d23270b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,37 +1,49 @@ plugins { id 'org.springframework.boot' version '2.2.4.RELEASE' id 'io.spring.dependency-management' version '1.0.9.RELEASE' + id "de.aaschmid.cpd" version "3.1" id 'java' } -group = 'ch.usi.inf.sa4.sanmarinoes.' +group = 'ch.usi.inf.sa4.sanmarinoes' version = '0.0.1-SNAPSHOT' -sourceCompatibility = "11" +sourceCompatibility = '11' repositories { mavenCentral() - jcenter() } 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' implementation 'org.springframework.boot:spring-boot-starter-mail' implementation 'io.jsonwebtoken:jjwt:0.9.1' implementation 'org.springframework.security:spring-security-web' implementation 'org.postgresql:postgresql' - compile "io.springfox:springfox-swagger2:2.9.2" - compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2' + implementation 'com.google.code.gson:gson' + 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' implementation('org.springframework.boot:spring-boot-starter-web') { exclude group: 'org.springframework.boot', module: 'spring-boot-starter-json' } - implementation 'com.google.code.gson:gson' + 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 { diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f3d88b1 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 52d63d1..a2bf131 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Thu Feb 20 21:04:58 CET 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip 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 new file mode 100644 index 0000000..7df826d --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/CORSFilter.java @@ -0,0 +1,51 @@ +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; + +/** + * Add CORS headers to each response in order to please the frontend requests, coming from a + * different host for now (thanks to the difference in ports). Andrea would you please stop + * complaining now + */ +@Component +public class CORSFilter implements Filter { + + 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"))); + } + + @Override + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) + throws IOException, ServletException { + final HttpServletResponse response = (HttpServletResponse) res; + + setCORSHeaders(response); + + chain.doFilter(req, res); + } + + @Override + public void init(FilterConfig filterConfig) {} + + @Override + public void destroy() {} +} 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 new file mode 100644 index 0000000..8bae55d --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/EmailConfigurationService.java @@ -0,0 +1,92 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.config; + +import javax.validation.constraints.NotNull; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; + +/** + * Class to interface with `email.*` properties in application.properties. This properties are used + * for generating the email to send on password reset or registration + * + * @see ch.usi.inf.sa4.sanmarinoes.smarthut.controller.UserAccountController + */ +@Component +@Validated +@EnableConfigurationProperties +@ConfigurationProperties(prefix = "email") +public class EmailConfigurationService { + + /** The email subject for a registration email */ + @NotNull private String registrationSubject; + + /** The text in the email body preceding the confirmation URL for a registration email */ + @NotNull private String registration; + + /** + * The URL to follow for registration email confirmation. Has to end with the start of a query + * parameter + */ + @NotNull private String registrationPath; + + /** 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; + + public String getRegistrationSubject() { + return registrationSubject; + } + + public void setRegistrationSubject(String registrationSubject) { + this.registrationSubject = registrationSubject; + } + + public String getRegistration() { + return registration; + } + + public void setRegistration(String registration) { + this.registration = registration; + } + + public String getRegistrationPath() { + return registrationPath; + } + + public void setRegistrationPath(String registrationPath) { + this.registrationPath = registrationPath; + } + + public String getResetPasswordSubject() { + return resetPasswordSubject; + } + + public void setResetPasswordSubject(String resetPasswordSubject) { + this.resetPasswordSubject = resetPasswordSubject; + } + + public String getResetPassword() { + return resetPassword; + } + + public void setResetPassword(String resetPassword) { + this.resetPassword = resetPassword; + } + + public String getResetPasswordPath() { + return resetPasswordPath; + } + + public void setResetPasswordPath(String resetPasswordPath) { + this.resetPasswordPath = resetPasswordPath; + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonConfig.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonConfig.java index 3a2ab37..e12d9f0 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonConfig.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonConfig.java @@ -23,6 +23,7 @@ public class GsonConfig { private Gson gson() { final GsonBuilder builder = new GsonBuilder(); builder.registerTypeAdapter(Json.class, new SpringfoxJsonToGsonAdapter()); + builder.addSerializationExclusionStrategy(new AnnotationExclusionStrategy()); return builder.create(); } } @@ -34,3 +35,16 @@ class SpringfoxJsonToGsonAdapter implements JsonSerializer { return JsonParser.parseString(json.value()); } } + +/** GSON exclusion strategy to exclude attributes with @GsonExclude */ +class AnnotationExclusionStrategy implements ExclusionStrategy { + @Override + public boolean shouldSkipField(FieldAttributes f) { + return f.getAnnotation(GsonExclude.class) != null; + } + + @Override + public boolean shouldSkipClass(Class clazz) { + return false; + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonExclude.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonExclude.java new file mode 100644 index 0000000..1be5551 --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonExclude.java @@ -0,0 +1,10 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.config; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface GsonExclude {} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/JWTAuthenticationEntryPoint.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/JWTAuthenticationEntryPoint.java index 7dfc16d..5c91217 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/JWTAuthenticationEntryPoint.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/JWTAuthenticationEntryPoint.java @@ -16,6 +16,10 @@ public class JWTAuthenticationEntryPoint implements AuthenticationEntryPoint { HttpServletResponse response, AuthenticationException authException) throws IOException { - response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); + if (!"OPTIONS".equals(request.getMethod())) { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); + } else { + CORSFilter.setCORSHeaders(response); + } } } 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 a8c86a5..2fdab4e 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 static springfox.documentation.builders.PathSelectors.regex; import java.util.List; import java.util.function.Predicate; @@ -8,11 +7,11 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; -import springfox.documentation.service.ApiInfo; -import springfox.documentation.service.ApiKey; -import springfox.documentation.service.SecurityScheme; +import springfox.documentation.service.*; import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.service.contexts.SecurityContext; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @@ -38,7 +37,8 @@ public class SpringFoxConfig { .paths(paths()::test) .build() .apiInfo(apiInfo()) - .securitySchemes(securitySchemes()); + .securitySchemes(securitySchemes()) + .securityContexts(List.of(securityContext())); } /** @@ -50,14 +50,32 @@ public class SpringFoxConfig { return List.of(new ApiKey("Bearer", "Authorization", "header")); } - /** - * Return a Java functional API predicate for regex matches - * - * @param regex the regex to match on - * @return a Java functional API predicate - */ - private Predicate regexPredicate(final String regex) { - return regex(regex)::apply; + private SecurityContext securityContext() { + return SecurityContext.builder() + .securityReferences(defaultAuth()) + .forPaths(authenticatedPaths()::test) + .build(); + } + + private List defaultAuth() { + final AuthorizationScope authorizationScope = + new AuthorizationScope("global", "accessEverything"); + return List.of( + new SecurityReference("Bearer", new AuthorizationScope[] {authorizationScope})); + } + + private Predicate authenticatedPaths() { + return ((Predicate) PathSelectors.regex("/auth/update")::apply) + .or(PathSelectors.regex("/room.*")::apply) + .or(PathSelectors.regex("/device.*")::apply) + .or(PathSelectors.regex("/buttonDimmer.*")::apply) + .or(PathSelectors.regex("/dimmableLight.*")::apply) + .or(PathSelectors.regex("/knobDimmer.*")::apply) + .or(PathSelectors.regex("/regularLight.*")::apply) + .or(PathSelectors.regex("/sensor.*")::apply) + .or(PathSelectors.regex("/smartPlug.*")::apply) + .or(PathSelectors.regex("/switch.*")::apply) + .or(PathSelectors.regex("/motionSensor.*")::apply); } /** @@ -67,9 +85,7 @@ public class SpringFoxConfig { * @return A predicate that tests whether a path must be included or not */ private Predicate paths() { - return regexPredicate("/auth.*") - .or(regexPredicate("/room.*")) - .or(regexPredicate("/register.*")); + return PathSelectors.any()::apply; } /** diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/WebSecurityConfig.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/WebSecurityConfig.java index e38d0df..253998d 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/WebSecurityConfig.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/WebSecurityConfig.java @@ -52,10 +52,11 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { .authorizeRequests() .antMatchers( "/auth/login", - "/auth/register", "/swagger-ui.html", "/register", "/register/confirm-account", + "/register/init-reset-password", + "/register/reset-password", "/v2/api-docs", "/webjars/**", "/swagger-resources/**", 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 cc9ed2e..1a1e266 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 @@ -4,6 +4,8 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.config.JWTTokenUtil; 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; @@ -17,7 +19,6 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.web.bind.annotation.*; @RestController -@CrossOrigin @RequestMapping("/auth") public class AuthenticationController { @@ -35,8 +36,7 @@ public class AuthenticationController { AuthenticationManager authenticationManager, UserRepository userRepository, JWTTokenUtil jwtTokenUtil, - JWTUserDetailsService userDetailsService, - UserRepository users) { + JWTUserDetailsService userDetailsService) { this.authenticationManager = authenticationManager; this.userRepository = userRepository; this.jwtTokenUtil = jwtTokenUtil; @@ -44,10 +44,30 @@ public class AuthenticationController { } @PostMapping("/login") - public JWTResponse login(@RequestBody JWTRequest authenticationRequest) throws Exception { - authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword()); - final UserDetails userDetails = - userDetailsService.loadUserByUsername(authenticationRequest.getUsername()); + public JWTResponse login(@Valid @RequestBody JWTRequest authenticationRequest) + throws UnauthorizedException, UserNotFoundException { + final UserDetails userDetails; + if (authenticationRequest.getUsernameOrEmail().contains("@")) { + // usernameOrEmail contains an email, so fetch the corresponding username + final User user = + userRepository.findByEmailIgnoreCase( + authenticationRequest.getUsernameOrEmail()); + if (user == null) { + throw new UserNotFoundException(); + } + + authenticate(user.getUsername(), authenticationRequest.getPassword()); + userDetails = userDetailsService.loadUserByUsername(user.getUsername()); + } else { + // usernameOrEmail contains a username, authenticate with that then + authenticate( + authenticationRequest.getUsernameOrEmail(), + authenticationRequest.getPassword()); + userDetails = + userDetailsService.loadUserByUsername( + authenticationRequest.getUsernameOrEmail()); + } + final String token = jwtTokenUtil.generateToken(userDetails); return new JWTResponse(token); } @@ -67,16 +87,14 @@ public class AuthenticationController { return userRepository.save(oldUser); } - private void authenticate(String username, String password) throws Exception { + private void authenticate(String username, String password) throws UnauthorizedException { try { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(username, password)); } catch (DisabledException e) { - e.printStackTrace(); - throw new Exception("USER_DISABLED", e); + throw new UnauthorizedException(true); } catch (BadCredentialsException e) { - e.printStackTrace(); - throw new Exception("INVALID_CREDENTIALS", e); + throw new UnauthorizedException(false); } } } 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 aa8cfa4..061c6b9 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 @@ -2,11 +2,12 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList; -import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.ButtonDimmerSaveRequest; -import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ButtonDimmer; -import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ButtonDimmerRepository; +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.util.List; -import java.util.Optional; +import java.util.Set; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -15,37 +16,77 @@ import org.springframework.web.bind.annotation.*; @RestController @EnableAutoConfiguration @RequestMapping("/buttonDimmer") -public class ButtonDimmerController { - @Autowired private ButtonDimmerRepository buttonDimmerService; +public class ButtonDimmerController + extends InputDeviceConnectionController { + private ButtonDimmerRepository buttonDimmerRepository; + private DimmableLightRepository dimmableLightRepository; + + @Autowired + protected ButtonDimmerController( + ButtonDimmerRepository inputRepository, DimmableLightRepository outputRepository) { + super( + inputRepository, + outputRepository, + DimmableLight.BUTTON_DIMMER_DIMMABLE_LIGHT_CONNECTOR); + this.buttonDimmerRepository = inputRepository; + this.dimmableLightRepository = outputRepository; + } @GetMapping public List findAll() { - return toList(buttonDimmerService.findAll()); + return toList(buttonDimmerRepository.findAll()); } @GetMapping("/{id}") - public Optional findById(@PathVariable("id") long id) { - return buttonDimmerService.findById(id); + public ButtonDimmer findById(@PathVariable("id") long id) throws NotFoundException { + return buttonDimmerRepository.findById(id).orElseThrow(NotFoundException::new); } @PostMapping - public ButtonDimmer create(@Valid @RequestBody final ButtonDimmerSaveRequest bd) { + public ButtonDimmer create(@Valid @RequestBody final GenericDeviceSaveReguest bd) { ButtonDimmer newBD = new ButtonDimmer(); - newBD.setLights(bd.getLights()); - newBD.setId(bd.getId()); newBD.setName(bd.getName()); newBD.setRoomId(bd.getRoomId()); - return buttonDimmerService.save(newBD); + return buttonDimmerRepository.save(newBD); } - @PutMapping - public ButtonDimmer update(@Valid @RequestBody ButtonDimmerSaveRequest bd) { - return this.create(bd); + @PutMapping("/dim") + public Set dim(@Valid @RequestBody final ButtonDimmerDimRequest bd) + throws NotFoundException { + final ButtonDimmer buttonDimmer = + buttonDimmerRepository.findById(bd.getId()).orElseThrow(NotFoundException::new); + + switch (bd.getDimType()) { + case UP: + buttonDimmer.increaseIntensity(); + break; + case DOWN: + buttonDimmer.decreaseIntensity(); + break; + } + + dimmableLightRepository.saveAll(buttonDimmer.getOutputs()); + + return buttonDimmer.getOutputs(); + } + + @PostMapping("/{id}/lights") + public Set addLight( + @PathVariable("id") long inputId, @RequestParam("lightId") Long lightId) + throws NotFoundException { + return addOutput(inputId, lightId); + } + + @DeleteMapping("/{id}/lights") + public Set removeLight( + @PathVariable("id") long inputId, @RequestParam("lightId") Long lightId) + throws NotFoundException { + return removeOutput(inputId, lightId); } @DeleteMapping("/{id}") public void delete(@PathVariable("id") long id) { - buttonDimmerService.deleteById(id); + buttonDimmerRepository.deleteById(id); } } 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 new file mode 100644 index 0000000..fa3242d --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DeviceController.java @@ -0,0 +1,44 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; + +import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.DeviceSaveRequest; +import ch.usi.inf.sa4.sanmarinoes.smarthut.error.BadDataException; +import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Device; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DeviceRepository; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.RoomRepository; +import javax.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@EnableAutoConfiguration +@RequestMapping("/device") +public class DeviceController { + + @Autowired private DeviceRepository deviceRepository; + @Autowired private RoomRepository roomRepository; + + @PutMapping + public Device update(@Valid @RequestBody DeviceSaveRequest deviceSaveRequest) + throws NotFoundException, BadDataException { + final Device d = + deviceRepository + .findById(deviceSaveRequest.getId()) + .orElseThrow(NotFoundException::new); + + // check if roomId is valid + roomRepository + .findById(deviceSaveRequest.getRoomId()) + .orElseThrow(() -> new BadDataException("roomId is not a valid room id")); + + d.setRoomId(deviceSaveRequest.getRoomId()); + d.setName(deviceSaveRequest.getName()); + + deviceRepository.save(d); + return d; + } +} 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 b02a519..dd2e1e3 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 @@ -3,10 +3,10 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList; 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.util.List; -import java.util.Optional; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -25,24 +25,28 @@ public class DimmableLightController { } @GetMapping("/{id}") - public Optional findById(@PathVariable("id") long id) { - return dimmableLightService.findById(id); + public DimmableLight findById(@PathVariable("id") long id) throws NotFoundException { + return dimmableLightService.findById(id).orElseThrow(NotFoundException::new); + } + + private DimmableLight save(DimmableLight initial, DimmableLightSaveRequest dl) { + initial.setIntensity(dl.getIntensity()); + initial.setName(dl.getName()); + initial.setRoomId(dl.getRoomId()); + + return dimmableLightService.save(initial); } @PostMapping public DimmableLight create(@Valid @RequestBody DimmableLightSaveRequest dl) { - DimmableLight newDL = new DimmableLight(); - newDL.setIntensity(dl.getIntensity()); - newDL.setId(dl.getId()); - newDL.setName(dl.getName()); - newDL.setRoomId(dl.getRoomId()); - - return dimmableLightService.save(newDL); + return save(new DimmableLight(), dl); } @PutMapping - public DimmableLight update(@Valid @RequestBody DimmableLightSaveRequest dl) { - return this.create(dl); + public DimmableLight update(@Valid @RequestBody DimmableLightSaveRequest sp) + throws NotFoundException { + return save( + dimmableLightService.findById(sp.getId()).orElseThrow(NotFoundException::new), sp); } @DeleteMapping("/{id}") diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/InputDeviceConnectionController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/InputDeviceConnectionController.java new file mode 100644 index 0000000..52f3483 --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/InputDeviceConnectionController.java @@ -0,0 +1,92 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; + +import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; +import java.util.Set; + +/** + * An abstract controller for an input device that has output connected to it. Aids to create the + * output add and output remove route + * + * @param the type of device this controller is for + * @param the output device attached to I + */ +public abstract class InputDeviceConnectionController< + I extends InputDevice, O extends OutputDevice> { + + private class IOPair { + private final I input; + private final O output; + + private IOPair(I input, O output) { + this.input = input; + this.output = output; + } + } + + private DeviceRepository inputRepository; + + private DeviceRepository outputReposiory; + + private Connector connector; + + /** + * Contstructs the controller by requiring essential object for the controller implementation + * + * @param inputRepository the input device repository + * @param outputRepository the output device repository + * @param connector a appropriate Connector instance for the I and O tyoes. + */ + protected InputDeviceConnectionController( + DeviceRepository inputRepository, + DeviceRepository outputRepository, + Connector connector) { + this.inputRepository = inputRepository; + this.outputReposiory = outputRepository; + this.connector = connector; + } + + private IOPair checkConnectionIDs(Long inputId, Long outputId) throws NotFoundException { + final I input = + inputRepository + .findById(inputId) + .orElseThrow(() -> new NotFoundException("input device")); + final O output = + outputReposiory + .findById(outputId) + .orElseThrow(() -> new NotFoundException("output device")); + return new IOPair(input, output); + } + + /** + * Implements the output device connection creation (add) route + * + * @param inputId input device id + * @param outputId output device id + * @return the list of output devices attached to the input device of id inputId + * @throws NotFoundException if inputId or outputId are not valid + */ + protected Set addOutput(Long inputId, Long outputId) + throws NotFoundException { + final IOPair pair = checkConnectionIDs(inputId, outputId); + connector.connect(pair.input, pair.output, true); + outputReposiory.save(pair.output); + return pair.input.getOutputs(); + } + + /** + * Implements the output device connection destruction (remove) route + * + * @param inputId input device id + * @param outputId output device id + * @return the list of output devices attached to the input device of id inputId + * @throws NotFoundException if inputId or outputId are not valid + */ + protected Set removeOutput(Long inputId, Long outputId) + throws NotFoundException { + final IOPair pair = checkConnectionIDs(inputId, outputId); + connector.connect(pair.input, pair.output, false); + outputReposiory.save(pair.output); + return pair.input.getOutputs(); + } +} 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 bd02d02..9f59889 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 @@ -2,11 +2,12 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList; -import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.KnobDimmerSaveRequest; -import ch.usi.inf.sa4.sanmarinoes.smarthut.models.KnobDimmer; -import ch.usi.inf.sa4.sanmarinoes.smarthut.models.KnobDimmerRepository; +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.util.List; -import java.util.Optional; +import java.util.Set; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -15,38 +16,70 @@ import org.springframework.web.bind.annotation.*; @RestController @EnableAutoConfiguration @RequestMapping("/knobDimmer") -public class KnobDimmerController { +public class KnobDimmerController + extends InputDeviceConnectionController { - @Autowired private KnobDimmerRepository knobDimmerService; + @Autowired private KnobDimmerRepository knobDimmerRepository; + @Autowired private DimmableLightRepository dimmableLightRepository; + + @Autowired + protected KnobDimmerController( + KnobDimmerRepository inputRepository, DimmableLightRepository outputRepository) { + super( + inputRepository, + outputRepository, + DimmableLight.KNOB_DIMMER_DIMMABLE_LIGHT_CONNECTOR); + this.knobDimmerRepository = inputRepository; + this.dimmableLightRepository = outputRepository; + } @GetMapping public List findAll() { - return toList(knobDimmerService.findAll()); + return toList(knobDimmerRepository.findAll()); } @GetMapping("/{id}") - public Optional findById(@PathVariable("id") long id) { - return knobDimmerService.findById(id); + public KnobDimmer findById(@PathVariable("id") long id) throws NotFoundException { + return knobDimmerRepository.findById(id).orElseThrow(NotFoundException::new); } @PostMapping - public KnobDimmer create(@Valid @RequestBody KnobDimmerSaveRequest kd) { + public KnobDimmer create(@Valid @RequestBody GenericDeviceSaveReguest kd) { KnobDimmer newKD = new KnobDimmer(); - newKD.setLights(kd.getLights()); - newKD.setId(kd.getId()); newKD.setName(kd.getName()); newKD.setRoomId(kd.getRoomId()); - return knobDimmerService.save(newKD); + return knobDimmerRepository.save(newKD); } - @PutMapping - public KnobDimmer update(@Valid @RequestBody KnobDimmerSaveRequest kd) { - return this.create(kd); + @PutMapping("/dimTo") + public Set dimTo(@Valid @RequestBody final KnobDimmerDimRequest bd) + throws NotFoundException { + final KnobDimmer dimmer = + knobDimmerRepository.findById(bd.getId()).orElseThrow(NotFoundException::new); + + dimmer.setLightIntensity(bd.getIntensity()); + dimmableLightRepository.saveAll(dimmer.getOutputs()); + + return dimmer.getOutputs(); + } + + @PostMapping("/{id}/lights") + public Set addLight( + @PathVariable("id") long inputId, @RequestParam("lightId") Long lightId) + throws NotFoundException { + return addOutput(inputId, lightId); + } + + @DeleteMapping("/{id}/lights") + public Set removeLight( + @PathVariable("id") long inputId, @RequestParam("lightId") Long lightId) + throws NotFoundException { + return removeOutput(inputId, lightId); } @DeleteMapping("/{id}") public void delete(@PathVariable("id") long id) { - knobDimmerService.deleteById(id); + knobDimmerRepository.deleteById(id); } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/MotionSensorController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/MotionSensorController.java index 4f9fc27..4ba03b1 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/MotionSensorController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/MotionSensorController.java @@ -2,11 +2,11 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList; -import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.MotionSensorSaveRequest; +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.MotionSensor; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.MotionSensorRepository; import java.util.List; -import java.util.Optional; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -25,26 +25,19 @@ public class MotionSensorController { } @GetMapping("/{id}") - public Optional findById(@PathVariable("id") long id) { - return motionSensorService.findById(id); + public MotionSensor findById(@PathVariable("id") long id) throws NotFoundException { + return motionSensorService.findById(id).orElseThrow(NotFoundException::new); } @PostMapping - public MotionSensor create(@Valid @RequestBody MotionSensorSaveRequest ms) { + public MotionSensor create(@Valid @RequestBody GenericDeviceSaveReguest ms) { MotionSensor newMS = new MotionSensor(); - newMS.setDetected(ms.isDetected()); - newMS.setId(ms.getId()); newMS.setName(ms.getName()); newMS.setRoomId(ms.getRoomId()); return motionSensorService.save(newMS); } - @PutMapping - public MotionSensor update(@Valid @RequestBody MotionSensorSaveRequest ms) { - return this.create(ms); - } - @DeleteMapping("/{id}") public void delete(@PathVariable("id") long id) { motionSensorService.deleteById(id); 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 bc86686..15ce2e8 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 @@ -3,10 +3,10 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList; 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.util.List; -import java.util.Optional; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -32,14 +32,11 @@ public class RegularLightController { } @GetMapping("/{id}") - public Optional findById(@PathVariable("id") long id) { - return regularLightService.findById(id); + public RegularLight findById(@PathVariable("id") long id) throws NotFoundException { + return regularLightService.findById(id).orElseThrow(NotFoundException::new); } - @PostMapping - public RegularLight create(@Valid @RequestBody RegularLightSaveRequest rl) { - RegularLight newRL = new RegularLight(); - newRL.setId(rl.getId()); + private RegularLight save(RegularLight newRL, RegularLightSaveRequest rl) { newRL.setName(rl.getName()); newRL.setRoomId(rl.getRoomId()); newRL.setOn(rl.isOn()); @@ -47,9 +44,16 @@ public class RegularLightController { return regularLightService.save(newRL); } + @PostMapping + public RegularLight create(@Valid @RequestBody RegularLightSaveRequest rl) { + return save(new RegularLight(), rl); + } + @PutMapping - public RegularLight update(@Valid @RequestBody RegularLightSaveRequest rl) { - return this.create(rl); + public RegularLight update(@Valid @RequestBody RegularLightSaveRequest rl) + throws NotFoundException { + return save( + regularLightService.findById(rl.getId()).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 8c51b23..fb42037 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 @@ -3,13 +3,13 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.RoomSaveRequest; +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 javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.*; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; @RestController @@ -29,26 +29,27 @@ public class RoomController { } @GetMapping("/{id}") - public @ResponseBody Optional findById(@PathVariable("id") long id) { - return roomRepository.findById(id); + public @ResponseBody Room findById(@PathVariable("id") long id) throws NotFoundException { + return roomRepository.findById(id).orElseThrow(NotFoundException::new); } private Room save(final RoomSaveRequest r, final Principal principal, boolean setWhenNull) { Room newRoom = new Room(); - final String username = ((UserDetails) principal).getUsername(); + final String username = principal.getName(); final Long userId = userRepository.findByUsername(username).getId(); final String img = r.getImage(); - final String icon = r.getIcon(); + final Room.Icon icon = r.getIcon(); newRoom.setUserId(userId); + newRoom.setName(r.getName()); if (img != null) { - newRoom.setImage(img.getBytes()); + newRoom.setImage(img); } else if (setWhenNull) { newRoom.setImage(null); } if (icon != null) { - newRoom.setIcon(icon.getBytes()); + newRoom.setIcon(icon); } else if (setWhenNull) { newRoom.setIcon(null); } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SensorController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SensorController.java index 3c16272..3412e0c 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SensorController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SensorController.java @@ -3,6 +3,7 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SensorSaveRequest; +import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; import java.util.*; import java.util.List; @@ -24,27 +25,20 @@ public class SensorController { } @GetMapping("/{id}") - public Optional findById(@PathVariable("id") long id) { - return sensorRepository.findById(id); + public Sensor findById(@PathVariable("id") long id) throws NotFoundException { + return sensorRepository.findById(id).orElseThrow(NotFoundException::new); } @PostMapping public Sensor create(@Valid @RequestBody SensorSaveRequest s) { Sensor newSensor = new Sensor(); newSensor.setSensor(s.getSensor()); - newSensor.setValue(s.getValue()); - newSensor.setId(s.getId()); newSensor.setName(s.getName()); newSensor.setRoomId(s.getRoomId()); return sensorRepository.save(newSensor); } - @PutMapping - public Sensor update(@Valid @RequestBody SensorSaveRequest s) { - return this.create(s); - } - @DeleteMapping("/{id}") public void deleteById(@PathVariable("id") long id) { sensorRepository.deleteById(id); 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 9a45b32..8a57430 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 @@ -3,6 +3,7 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; 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.util.*; import java.util.List; @@ -24,13 +25,11 @@ public class SmartPlugController { } @GetMapping("/{id}") - public Optional findById(@PathVariable("id") long id) { - return smartPlugRepository.findById(id); + public SmartPlug findById(@PathVariable("id") long id) throws NotFoundException { + return smartPlugRepository.findById(id).orElseThrow(NotFoundException::new); } - @PostMapping - public SmartPlug create(@Valid @RequestBody SmartPlugSaveRequest sp) { - SmartPlug newSP = new SmartPlug(); + private SmartPlug save(SmartPlug newSP, SmartPlugSaveRequest sp) { newSP.setOn(sp.isOn()); newSP.setId(sp.getId()); newSP.setName(sp.getName()); @@ -39,9 +38,15 @@ public class SmartPlugController { return smartPlugRepository.save(newSP); } + @PostMapping + public SmartPlug create(@Valid @RequestBody SmartPlugSaveRequest sp) { + return save(new SmartPlug(), sp); + } + @PutMapping - public SmartPlug update(@Valid @RequestBody SmartPlugSaveRequest sp) { - return this.create(sp); + public SmartPlug update(@Valid @RequestBody SmartPlugSaveRequest sp) throws NotFoundException { + return save( + smartPlugRepository.findById(sp.getId()).orElseThrow(NotFoundException::new), sp); } @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 e7d9eef..f90108c 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 @@ -2,7 +2,9 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList; -import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SwitchSaveRequest; +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.util.*; import java.util.List; @@ -14,9 +16,24 @@ import org.springframework.web.bind.annotation.*; @RestController @EnableAutoConfiguration @RequestMapping("/switch") -public class SwitchController { +public class SwitchController extends InputDeviceConnectionController { - @Autowired private SwitchRepository switchRepository; + private SwitchRepository switchRepository; + private SwitchableRepository switchableRepository; + + /** + * Contstructs the controller by requiring essential object for the controller implementation + * + * @param inputRepository the input device repository + * @param outputRepository the output device repository + */ + @Autowired + protected SwitchController( + SwitchRepository inputRepository, SwitchableRepository outputRepository) { + super(inputRepository, outputRepository, Switchable.SWITCH_SWITCHABLE_CONNECTOR); + this.switchRepository = inputRepository; + this.switchableRepository = outputRepository; + } @GetMapping public List findAll() { @@ -24,24 +41,53 @@ public class SwitchController { } @GetMapping("/{id}") - public Optional findById(@PathVariable("id") long id) { - return switchRepository.findById(id); + public Switch findById(@PathVariable("id") long id) throws NotFoundException { + return switchRepository.findById(id).orElseThrow(NotFoundException::new); } @PostMapping - public Switch create(@Valid @RequestBody SwitchSaveRequest s) { + public Switch create(@Valid @RequestBody GenericDeviceSaveReguest s) { Switch newSwitch = new Switch(); - newSwitch.setId(s.getId()); newSwitch.setName(s.getName()); newSwitch.setRoomId(s.getRoomId()); - newSwitch.setOn(s.isOn()); return switchRepository.save(newSwitch); } - @PutMapping - public Switch update(@Valid @RequestBody SwitchSaveRequest s) { - return this.create(s); + @PutMapping("/operate") + public Set operate(@Valid @RequestBody final SwitchOperationRequest sr) + throws NotFoundException { + final Switch s = switchRepository.findById(sr.getId()).orElseThrow(NotFoundException::new); + + switch (sr.getType()) { + case ON: + s.setOn(true); + break; + case OFF: + s.setOn(false); + break; + case TOGGLE: + s.toggle(); + break; + } + + switchableRepository.saveAll(s.getOutputs()); + + return s.getOutputs(); + } + + @PostMapping("/{id}/lights") + public Set addSwitchable( + @PathVariable("id") long inputId, @RequestParam("switchableId") Long switchableId) + throws NotFoundException { + return addOutput(inputId, switchableId); + } + + @DeleteMapping("/{id}/lights") + public Set removeSwitchable( + @PathVariable("id") long inputId, @RequestParam("switchableId") Long switchableId) + throws NotFoundException { + return removeOutput(inputId, switchableId); } @DeleteMapping("/{id}") 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 e238f16..ebf354f 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 @@ -1,9 +1,13 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; +import ch.usi.inf.sa4.sanmarinoes.smarthut.config.EmailConfigurationService; +import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.InitPasswordResetRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.OkResponse; +import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.PasswordResetRequest; 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.EmailTokenNotFoundException; +import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UserNotFoundException; 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; @@ -11,30 +15,66 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository; import ch.usi.inf.sa4.sanmarinoes.smarthut.service.EmailSenderService; import javax.validation.Valid; import javax.validation.constraints.NotNull; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.mail.SimpleMailMessage; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; +/** Unauthenticated set of endpoints to handle registration and password reset */ @RestController @EnableAutoConfiguration @RequestMapping("/register") public class UserAccountController { - @Autowired private UserRepository userRepository; + private final UserRepository userRepository; - @Autowired private ConfirmationTokenRepository confirmationTokenRepository; + private final ConfirmationTokenRepository confirmationTokenRepository; - @Autowired private EmailSenderService emailSenderService; + private final EmailSenderService emailSenderService; - @Autowired private BCryptPasswordEncoder encoder; + private final BCryptPasswordEncoder encoder; + private final EmailConfigurationService emailConfig; + + public UserAccountController( + UserRepository userRepository, + ConfirmationTokenRepository confirmationTokenRepository, + EmailSenderService emailSenderService, + BCryptPasswordEncoder encoder, + EmailConfigurationService emailConfig) { + this.userRepository = userRepository; + this.confirmationTokenRepository = confirmationTokenRepository; + this.emailSenderService = emailSenderService; + this.encoder = encoder; + this.emailConfig = emailConfig; + } + + private void sendEmail(String email, ConfirmationToken token, boolean isRegistration) { + SimpleMailMessage mailMessage = new SimpleMailMessage(); + mailMessage.setTo(email); + mailMessage.setSubject( + isRegistration + ? emailConfig.getRegistrationSubject() + : emailConfig.getResetPasswordSubject()); + mailMessage.setFrom("smarthut.sm@gmail.com"); + mailMessage.setText( + (isRegistration ? emailConfig.getRegistration() : emailConfig.getResetPassword()) + + " " + + (isRegistration + ? emailConfig.getRegistrationPath() + : emailConfig.getResetPasswordPath()) + + token.getConfirmationToken()); + + emailSenderService.sendEmail(mailMessage); + } + + /** + * Unauthenticated endpoint to call to send a password reset email + * + * @param registrationData registration data of the new user + * @return success + * @throws DuplicateRegistrationException if a user exists with same email or username + */ @PostMapping public OkResponse registerUser(@Valid @RequestBody UserRegistrationRequest registrationData) throws DuplicateRegistrationException { @@ -60,35 +100,101 @@ public class UserAccountController { toSave.setEmail(registrationData.getEmail()); userRepository.save(toSave); - ConfirmationToken confirmationToken = new ConfirmationToken(toSave); + ConfirmationToken token; + do { + token = new ConfirmationToken(toSave); + } while (confirmationTokenRepository.findByConfirmationToken( + token.getConfirmationToken()) + != null); - confirmationTokenRepository.save(confirmationToken); + confirmationTokenRepository.save(token); - SimpleMailMessage mailMessage = new SimpleMailMessage(); - mailMessage.setTo(registrationData.getEmail()); - mailMessage.setSubject("Complete Registration!"); - mailMessage.setFrom("smarthut.sm@gmail.com"); - mailMessage.setText( - "To confirm your account, please click here : " - + "http://localhost:8080/register/confirm-account?token=" - + confirmationToken.getConfirmationToken()); - - emailSenderService.sendEmail(mailMessage); + sendEmail(toSave.getEmail(), token, true); return new OkResponse(); } } + /** + * Unauthenticated endpoint to call to send a password reset email + * + * @param resetRequest a JSON object containing the email of the user to reset + * @return success + * @throws UserNotFoundException if given email does not belong to any user + */ + @PostMapping("/init-reset-password") + public OkResponse initResetPassword(@Valid @RequestBody InitPasswordResetRequest resetRequest) + throws UserNotFoundException { + final User toReset = userRepository.findByEmailIgnoreCase(resetRequest.getEmail()); + + // Check if an User with the same email already exists + if (toReset == null) { + throw new UserNotFoundException(); + } + + ConfirmationToken token; + do { + token = new ConfirmationToken(toReset); + token.setResetPassword(true); + } while (confirmationTokenRepository.findByConfirmationToken(token.getConfirmationToken()) + != null); + + // Delete existing email password reset tokens + confirmationTokenRepository.deleteByUserAndResetPassword(toReset, true); + + // Save new token + confirmationTokenRepository.save(token); + + sendEmail(toReset.getEmail(), token, false); + + return new OkResponse(); + } + + /** + * Unauthenticated endpoint to call with token sent by email to reset password + * + * @param resetRequest the token given via email and the new password + * @return success + * @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 { + final ConfirmationToken token = + confirmationTokenRepository.findByConfirmationToken( + resetRequest.getConfirmationToken()); + + if (token == null || !token.getResetPassword()) { + throw new EmailTokenNotFoundException(); + } + + final User user = token.getUser(); + user.setPassword(encoder.encode(resetRequest.getPassword())); + userRepository.save(user); + + // Delete token to prevent further password changes + confirmationTokenRepository.delete(token); + + return new OkResponse(); + } + + /** + * Unauthenticated endpoint to call with token sent by email to enable user + * + * @param confirmationToken the token given via email + * @return success + * @throws EmailTokenNotFoundException if given token is not a valid token for email + * confirmation + */ @GetMapping(value = "/confirm-account") public OkResponse confirmUserAccount(@RequestParam("token") @NotNull String confirmationToken) throws EmailTokenNotFoundException { final ConfirmationToken token = confirmationTokenRepository.findByConfirmationToken(confirmationToken); - if (token != null) { - final User user = userRepository.findByEmailIgnoreCase(token.getUser().getEmail()); - user.setEnabled(true); - userRepository.save(user); + if (token != null && !token.getResetPassword()) { + token.getUser().setEnabled(true); + userRepository.save(token.getUser()); // TODO: redirect to frontend return new OkResponse(); } else { diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/WelcomeController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/WelcomeController.java deleted file mode 100644 index 8cbb5d5..0000000 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/WelcomeController.java +++ /dev/null @@ -1,23 +0,0 @@ -package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; - -import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; -import java.util.*; -import org.springframework.boot.autoconfigure.*; -import org.springframework.web.bind.annotation.*; - -@RestController -@EnableAutoConfiguration -// @Mapping("/light") -public class WelcomeController { - - @GetMapping - List testDevices() { - return Arrays.asList( - new KnobDimmer(), - new RegularLight(), - new MotionSensor(), - new Sensor(), - new SmartPlug(), - new Switch()); - } -} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/ButtonDimmerDimRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/ButtonDimmerDimRequest.java new file mode 100644 index 0000000..8e07015 --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/ButtonDimmerDimRequest.java @@ -0,0 +1,34 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.dto; + +import javax.validation.constraints.NotNull; + +/** A 'dim' event from a button dimmer. */ +public class ButtonDimmerDimRequest { + + /** The device id */ + @NotNull private Long id; + + public enum DimType { + UP, + DOWN; + } + + /** Whether the dim is up or down */ + @NotNull private DimType dimType; + + public DimType getDimType() { + return dimType; + } + + public void setDimType(DimType dimType) { + this.dimType = dimType; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/ButtonDimmerSaveRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/ButtonDimmerSaveRequest.java deleted file mode 100644 index ce49970..0000000 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/ButtonDimmerSaveRequest.java +++ /dev/null @@ -1,67 +0,0 @@ -package ch.usi.inf.sa4.sanmarinoes.smarthut.dto; - -import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DimmableLight; -import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Room; -import java.util.HashSet; -import java.util.Set; -import javax.persistence.*; -import javax.validation.constraints.NotNull; - -public class ButtonDimmerSaveRequest { - @Lob private Set lights = new HashSet(); - - /** Device identifier */ - private long id; - - /** The room this device belongs in */ - private Room room; - - /** - * The room this device belongs in, as a foreign key id. To use when updating and inserting from - * a REST call. - */ - @NotNull private Long roomId; - - /** The name of the device as assigned by the user (e.g. 'Master bedroom light') */ - @NotNull private String name; - - public Set getLights() { - return this.lights; - } - - public void setLights(Set newLights) { - this.lights = newLights; - } - - public void setId(long id) { - this.id = id; - } - - public void setRoom(Room room) { - this.room = room; - } - - public void setRoomId(Long roomId) { - this.roomId = roomId; - } - - public void setName(String name) { - this.name = name; - } - - public long getId() { - return id; - } - - public Room getRoom() { - return room; - } - - public Long getRoomId() { - return roomId; - } - - public String getName() { - return name; - } -} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SwitchSaveRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/DeviceSaveRequest.java similarity index 76% rename from src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SwitchSaveRequest.java rename to src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/DeviceSaveRequest.java index 84142ec..a975117 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SwitchSaveRequest.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/DeviceSaveRequest.java @@ -1,11 +1,9 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.dto; +import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; -public class SwitchSaveRequest { - /** The state of this switch */ - private boolean on; - +public class DeviceSaveRequest { /** Device identifier */ private long id; @@ -16,37 +14,29 @@ public class SwitchSaveRequest { @NotNull private Long roomId; /** The name of the device as assigned by the user (e.g. 'Master bedroom light') */ - @NotNull private String name; - - public void setId(long id) { - this.id = id; - } - - public void setRoomId(Long roomId) { - this.roomId = roomId; - } - - public void setName(String name) { - this.name = name; - } + @NotNull @NotEmpty private String name; public long getId() { return id; } + public void setId(long id) { + this.id = id; + } + public Long getRoomId() { return roomId; } + public void setRoomId(Long roomId) { + this.roomId = roomId; + } + public String getName() { return name; } - public boolean isOn() { - return on; - } - - public void setOn(boolean on) { - this.on = on; + public void setName(String name) { + this.name = name; } } 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 43b5a97..01bec1a 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 @@ -1,24 +1,20 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.dto; -import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Room; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; public class DimmableLightSaveRequest { + /** Device id (used only for update requests) */ + private Long id; + /** The light intensity value. Goes from 0 (off) to 100 (on) */ @NotNull @Min(1) @Max(100) private Integer intensity = 0; - /** Device identifier */ - private long id; - - /** The room this device belongs in */ - private Room room; - /** * The room this device belongs in, as a foreign key id. To use when updating and inserting from * a REST call. @@ -28,14 +24,6 @@ public class DimmableLightSaveRequest { /** The name of the device as assigned by the user (e.g. 'Master bedroom light') */ @NotNull private String name; - public void setId(long id) { - this.id = id; - } - - public void setRoom(Room room) { - this.room = room; - } - public void setRoomId(Long roomId) { this.roomId = roomId; } @@ -44,14 +32,6 @@ public class DimmableLightSaveRequest { this.name = name; } - public long getId() { - return id; - } - - public Room getRoom() { - return room; - } - public Long getRoomId() { return roomId; } @@ -64,10 +44,15 @@ public class DimmableLightSaveRequest { return intensity; } - public void setIntensity(Integer intensity) throws IllegalArgumentException { - if (intensity < 0 || intensity > 100) { - throw new IllegalArgumentException("The intensity level can't go below 0 or above 100"); - } + public void setIntensity(Integer intensity) { this.intensity = intensity; } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/MotionSensorSaveRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/GenericDeviceSaveReguest.java similarity index 62% rename from src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/MotionSensorSaveRequest.java rename to src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/GenericDeviceSaveReguest.java index 50976b6..8ec2671 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/MotionSensorSaveRequest.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/GenericDeviceSaveReguest.java @@ -2,12 +2,7 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.dto; import javax.validation.constraints.NotNull; -public class MotionSensorSaveRequest { - private boolean detected; - - /** Device identifier */ - private long id; - +public class GenericDeviceSaveReguest { /** * The room this device belongs in, as a foreign key id. To use when updating and inserting from * a REST call. @@ -17,10 +12,6 @@ public class MotionSensorSaveRequest { /** The name of the device as assigned by the user (e.g. 'Master bedroom light') */ @NotNull private String name; - public void setId(long id) { - this.id = id; - } - public void setRoomId(Long roomId) { this.roomId = roomId; } @@ -29,10 +20,6 @@ public class MotionSensorSaveRequest { this.name = name; } - public long getId() { - return id; - } - public Long getRoomId() { return roomId; } @@ -40,12 +27,4 @@ public class MotionSensorSaveRequest { public String getName() { return name; } - - public boolean isDetected() { - return detected; - } - - public void setDetected(boolean detected) { - this.detected = detected; - } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/InitPasswordResetRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/InitPasswordResetRequest.java new file mode 100644 index 0000000..d82c4f0 --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/InitPasswordResetRequest.java @@ -0,0 +1,25 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.dto; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.Pattern; + +/** DTO for password reset request */ +public class InitPasswordResetRequest { + /** + * The user's email (validated according to criteria used in >input type="email"<> + * , technically not RFC 5322 compliant + */ + @NotEmpty(message = "Please provide an email") + @Email(message = "Please provide a valid email address") + @Pattern(regexp = ".+@.+\\..+", message = "Please provide a valid email address") + private String email; + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/JWTRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/JWTRequest.java index d750a50..da11bc3 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/JWTRequest.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/JWTRequest.java @@ -1,15 +1,17 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.dto; -public class JWTRequest { - private String username; - private String password; +import javax.validation.constraints.NotNull; - public String getUsername() { - return this.username; +public class JWTRequest { + @NotNull private String usernameOrEmail; + @NotNull private String password; + + public String getUsernameOrEmail() { + return this.usernameOrEmail; } - public void setUsername(String username) { - this.username = username; + public void setUsernameOrEmail(String usernameOrEmail) { + this.usernameOrEmail = usernameOrEmail; } public String getPassword() { @@ -19,4 +21,16 @@ public class JWTRequest { public void setPassword(String password) { this.password = password; } + + @Override + public String toString() { + return "JWTRequest{" + + "usernameOrEmail='" + + usernameOrEmail + + '\'' + + ", password='" + + password + + '\'' + + '}'; + } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/KnobDimmerDimRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/KnobDimmerDimRequest.java new file mode 100644 index 0000000..6df303a --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/KnobDimmerDimRequest.java @@ -0,0 +1,33 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.dto; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; + +public class KnobDimmerDimRequest { + + /** The device id */ + @NotNull private Long id; + + /** The absolute intensity value */ + @NotNull + @Min(0) + @Max(100) + private Integer intensity; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Integer getIntensity() { + return intensity; + } + + public void setIntensity(Integer intensity) { + this.intensity = intensity; + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/KnobDimmerSaveRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/KnobDimmerSaveRequest.java deleted file mode 100644 index bfc3ba8..0000000 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/KnobDimmerSaveRequest.java +++ /dev/null @@ -1,55 +0,0 @@ -package ch.usi.inf.sa4.sanmarinoes.smarthut.dto; - -import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DimmableLight; -import java.util.HashSet; -import java.util.Set; -import javax.persistence.Lob; -import javax.validation.constraints.NotNull; - -public class KnobDimmerSaveRequest { - @Lob private Set lights = new HashSet(); - - /** Device identifier */ - private long id; - - /** - * The room this device belongs in, as a foreign key id. To use when updating and inserting from - * a REST call. - */ - @NotNull private Long roomId; - - /** The name of the device as assigned by the user (e.g. 'Master bedroom light') */ - @NotNull private String name; - - public void setId(long id) { - this.id = id; - } - - public void setRoomId(Long roomId) { - this.roomId = roomId; - } - - public void setName(String name) { - this.name = name; - } - - public long getId() { - return id; - } - - public Long getRoomId() { - return roomId; - } - - public String getName() { - return name; - } - - public void setLights(Set lights) { - this.lights = lights; - } - - public Set getLights() { - return lights; - } -} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/PasswordResetRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/PasswordResetRequest.java new file mode 100644 index 0000000..bf5bccf --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/PasswordResetRequest.java @@ -0,0 +1,34 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.dto; + +import javax.validation.constraints.*; + +/** DTO for password reset request */ +public class PasswordResetRequest { + + @NotNull private String confirmationToken; + + /** A properly salted way to store the password */ + @NotNull + @NotEmpty(message = "Please provide a password") + @Size( + min = 6, + max = 255, + message = "Your password should be at least 6 characters long and up to 255 chars long") + private String password; + + public String getConfirmationToken() { + return confirmationToken; + } + + public void setConfirmationToken(String confirmationToken) { + this.confirmationToken = confirmationToken; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/RegularLightSaveRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/RegularLightSaveRequest.java index 34695e2..99211e5 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/RegularLightSaveRequest.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/RegularLightSaveRequest.java @@ -18,10 +18,6 @@ public class RegularLightSaveRequest { /** The name of the device as assigned by the user (e.g. 'Master bedroom light') */ @NotNull private String name; - public void setId(long id) { - this.id = id; - } - public void setRoomId(Long roomId) { this.roomId = roomId; } @@ -49,4 +45,8 @@ public class RegularLightSaveRequest { public void setOn(boolean on) { this.on = on; } + + public void setId(long id) { + this.id = id; + } } 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 f813b1c..ca8fa0f 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 @@ -1,17 +1,19 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.dto; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Room; import javax.persistence.Lob; import javax.validation.constraints.NotNull; public class RoomSaveRequest { + + @NotNull private Room.Icon icon; + /** - * Icon and image are to be given as byte[]. In order to get an encoded string from it, the + * Image is to be given as byte[]. In order to get an encoded string from it, the * Base64.getEncoder().encodeToString(byte[] content) should be used. For further information: * https://www.baeldung.com/java-base64-image-string * https://docs.oracle.com/javase/8/docs/api/java/util/Base64.html */ - @Lob private String icon; - @Lob private String image; /** The user given name of this room (e.g. 'Master bedroom') */ @@ -25,11 +27,11 @@ public class RoomSaveRequest { this.name = name; } - public String getIcon() { + public Room.Icon getIcon() { return icon; } - public void setIcon(String icon) { + public void setIcon(Room.Icon icon) { this.icon = icon; } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SensorSaveRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SensorSaveRequest.java index 47ca739..e9a5c68 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SensorSaveRequest.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SensorSaveRequest.java @@ -23,17 +23,11 @@ public class SensorSaveRequest { LIGHT } - /** The value of this sensor according to its sensor type */ - private int value; - /** The type of this sensor */ @NotNull @Enumerated(value = EnumType.STRING) private Sensor.SensorType sensor; - /** Device identifier */ - private long id; - /** * The room this device belongs in, as a foreign key id. To use when updating and inserting from * a REST call. @@ -43,10 +37,6 @@ public class SensorSaveRequest { /** The name of the device as assigned by the user (e.g. 'Master bedroom light') */ @NotNull private String name; - public void setId(long id) { - this.id = id; - } - public void setRoomId(Long roomId) { this.roomId = roomId; } @@ -55,10 +45,6 @@ public class SensorSaveRequest { this.name = name; } - public long getId() { - return id; - } - public Long getRoomId() { return roomId; } @@ -74,12 +60,4 @@ public class SensorSaveRequest { public void setSensor(Sensor.SensorType sensor) { this.sensor = sensor; } - - public int getValue() { - return this.value; - } - - public void setValue(int newValue) { - this.value = newValue; - } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SmartPlugSaveRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SmartPlugSaveRequest.java index 7511341..6b2f9b5 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SmartPlugSaveRequest.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SmartPlugSaveRequest.java @@ -18,10 +18,6 @@ public class SmartPlugSaveRequest { /** The name of the device as assigned by the user (e.g. 'Master bedroom light') */ @NotNull private String name; - public void setId(long id) { - this.id = id; - } - public void setRoomId(Long roomId) { this.roomId = roomId; } @@ -49,4 +45,8 @@ public class SmartPlugSaveRequest { public void setOn(boolean on) { this.on = on; } + + public void setId(long id) { + this.id = id; + } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SwitchOperationRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SwitchOperationRequest.java new file mode 100644 index 0000000..3fb552b --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SwitchOperationRequest.java @@ -0,0 +1,35 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.dto; + +import javax.validation.constraints.NotNull; + +/** An on/off/toggle operation on a switch */ +public class SwitchOperationRequest { + + /** The device id */ + @NotNull private Long id; + + public enum OperationType { + ON, + OFF, + TOGGLE + } + + /** The type of switch operation */ + @NotNull private SwitchOperationRequest.OperationType type; + + public OperationType getType() { + return type; + } + + public void setType(OperationType type) { + this.type = type; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/UserRegistrationRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/UserRegistrationRequest.java index b41720a..785d408 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/UserRegistrationRequest.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/UserRegistrationRequest.java @@ -12,6 +12,9 @@ public class UserRegistrationRequest { /** The full name of the user */ @NotNull @NotEmpty(message = "Please provide a username") + @Pattern( + regexp = "[A-Za-z0-9_\\-]+", + message = "Username can contain only letters, numbers, '_' and '-'") private String username; /** A properly salted way to store the password */ diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/error/BadDataException.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/error/BadDataException.java new file mode 100644 index 0000000..2c6c4d4 --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/error/BadDataException.java @@ -0,0 +1,11 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.error; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.BAD_REQUEST) +public class BadDataException extends Exception { + public BadDataException(String message) { + super(message); + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/error/NotFoundException.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/error/NotFoundException.java new file mode 100644 index 0000000..471107f --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/error/NotFoundException.java @@ -0,0 +1,15 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.error; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.NOT_FOUND) +public class NotFoundException extends Exception { + public NotFoundException() { + super("Not found"); + } + + public NotFoundException(String what) { + super(what + " not found"); + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/error/UnauthorizedException.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/error/UnauthorizedException.java new file mode 100644 index 0000000..9176df6 --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/error/UnauthorizedException.java @@ -0,0 +1,18 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.error; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.UNAUTHORIZED) +public class UnauthorizedException extends Exception { + private final boolean isUserDisabled; + + public UnauthorizedException(boolean isDisabled) { + super("Access denied: " + (isDisabled ? "user is disabled" : "wrong credentials")); + this.isUserDisabled = isDisabled; + } + + public boolean isUserDisabled() { + return isUserDisabled; + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/error/UserNotFoundException.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/error/UserNotFoundException.java new file mode 100644 index 0000000..d2e93f6 --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/error/UserNotFoundException.java @@ -0,0 +1,11 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.error; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.BAD_REQUEST) +public class UserNotFoundException extends Exception { + public UserNotFoundException() { + super("No user found with given email"); + } +} 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 1e5f702..fc91c22 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 @@ -1,9 +1,6 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; - -import java.util.Set; import javax.persistence.Entity; -import javax.persistence.OneToMany; /** * Represents a dimmer that can only instruct an increase or decrease of intensity (i.e. like a @@ -11,60 +8,25 @@ import javax.persistence.OneToMany; */ @Entity public class ButtonDimmer extends Dimmer { + + /** The delta amount to apply to a increase or decrease intensity */ + private static final int DIM_INCREMENT = 10; + public ButtonDimmer() { super("button-dimmer"); } - @OneToMany(mappedBy = "dimmer") - private Set lights; - - /** Increases the current intensity level of the dimmable light by 1 */ + /** Increases the current intensity level of the dimmable light by DIM_INCREMENT */ public void increaseIntensity() { - for (DimmableLight dl : lights) { - dl.setIntensity(dl.getIntensity() + 1); + for (DimmableLight dl : getOutputs()) { + dl.setIntensity(dl.getIntensity() + DIM_INCREMENT); } } - /** Decreases the current intensity level of the dimmable light by 1 */ + /** Decreases the current intensity level of the dimmable light by DIM_INCREMENT */ public void decreaseIntensity() { - for (DimmableLight dl : lights) { - dl.setIntensity(dl.getIntensity() - 1); + for (DimmableLight dl : getOutputs()) { + dl.setIntensity(dl.getIntensity() - DIM_INCREMENT); } } - - /** - * Adds a DimmableLight to this set of DimmableLights - * - * @param dl The DimmableLight to be added - */ - public void addLight(DimmableLight dl) { - lights.add(dl); - } - - /** - * Removes the given DimmableLight - * - * @param dl The DimmableLight to be removed - */ - public void removeLight(DimmableLight dl) { - lights.remove(dl); - } - - /** Clears this set */ - public void clearSet() { - lights.clear(); - } - - /** - * Get the lights - * - * @return duh - */ - public Set getLights() { - return this.lights; - } - - public void setLights(Set newLights) { - this.lights = newLights; - } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ConfirmationToken.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ConfirmationToken.java index f6c86a0..d324724 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ConfirmationToken.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ConfirmationToken.java @@ -21,7 +21,7 @@ public class ConfirmationToken { @Column(name = "id", updatable = false, nullable = false) private Long id; - @Column(name = "confirmation_token") + @Column(name = "confirmation_token", unique = true) private String confirmationToken; @Temporal(TemporalType.TIMESTAMP) @@ -31,10 +31,14 @@ public class ConfirmationToken { @JoinColumn(nullable = false, name = "user_id") private User user; + @Column(nullable = false) + private Boolean resetPassword; + public ConfirmationToken(User user) { this.user = user; createdDate = new Date(); confirmationToken = UUID.randomUUID().toString(); + resetPassword = false; } /** Constructor for hibernate reflective stuff things whatever */ @@ -71,4 +75,12 @@ public class ConfirmationToken { public void setUser(User user) { this.user = user; } + + public Boolean getResetPassword() { + return resetPassword; + } + + public void setResetPassword(Boolean resetPassword) { + this.resetPassword = resetPassword; + } } 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 4bc18ce..9bf3791 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 @@ -1,7 +1,11 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; +import javax.transaction.Transactional; import org.springframework.data.repository.CrudRepository; public interface ConfirmationTokenRepository extends CrudRepository { ConfirmationToken findByConfirmationToken(String confirmationToken); + + @Transactional + void deleteByUserAndResetPassword(User user, boolean resetPassword); } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Connector.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Connector.java new file mode 100644 index 0000000..91b1e88 --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Connector.java @@ -0,0 +1,47 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.models; + +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Function; + +/** + * A rule on how to connect an input device type to an output device type + * + * @param the input device type + * @param the output device type + */ +@FunctionalInterface +public interface Connector { + + /** + * Connects or disconnects input to output + * + * @param input the input device + * @param output the output device + * @param connect true if connection, false if disconnection + */ + void connect(I input, O output, boolean connect); + + /** + * Produces a basic implementation of a connector, assuming there is a OneToMany relationship + * between J and K + * + * @param outputsGetter the getter method of the set of outputs on the input class + * @param inputSetter the setter method for the input id on the output class + * @param the input device type + * @param the output device type + * @return a Connector implementation for the pair of types J and K + */ + static Connector basic( + Function> outputsGetter, BiConsumer inputSetter) { + return (i, o, connect) -> { + if (connect) { + outputsGetter.apply(i).add(o); + } else { + outputsGetter.apply(i).remove(o); + } + + inputSetter.accept(o, connect ? i.getId() : null); + }; + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableLight.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableLight.java index a71afbc..9a75471 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableLight.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableLight.java @@ -1,7 +1,9 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; +import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.validation.constraints.Max; import javax.validation.constraints.Min; @@ -9,18 +11,31 @@ import javax.validation.constraints.NotNull; /** Represent a dimmable light */ @Entity -public class DimmableLight extends Light { +public class DimmableLight extends Switchable { + + public static final Connector + BUTTON_DIMMER_DIMMABLE_LIGHT_CONNECTOR = + Connector.basic(ButtonDimmer::getOutputs, DimmableLight::setDimmerId); + + public static final Connector KNOB_DIMMER_DIMMABLE_LIGHT_CONNECTOR = + Connector.basic(KnobDimmer::getOutputs, DimmableLight::setDimmerId); public DimmableLight() { super("light"); } - @ManyToOne private Dimmer dimmer; + @ManyToOne + @GsonExclude + @JoinColumn(name = "dimmer_id", updatable = false, insertable = false) + private Dimmer dimmer; + + @Column(name = "dimmer_id") + private Long dimmerId; /** The light intensity value. Goes from 0 (off) to 100 (on) */ @NotNull @Column(nullable = false) - @Min(1) + @Min(0) @Max(100) private Integer intensity = 0; @@ -28,10 +43,41 @@ public class DimmableLight extends Light { return intensity; } - public void setIntensity(Integer intensity) throws IllegalArgumentException { - if (intensity < 0 || intensity > 100) { - throw new IllegalArgumentException("The intensity level can't go below 0 or above 100"); + /** + * Sets the intensity to a certain level. Out of bound values are corrected to the respective + * extremums. An intensity level of 0 turns the light off, but keeps the old intensity level + * stored. + * + * @param intensity the intensity level (may be out of bounds) + */ + public void setIntensity(Integer intensity) { + if (intensity <= 0) { + this.intensity = 0; + } else if (intensity > 100) { + this.intensity = 100; + } else { + this.intensity = intensity; } - this.intensity = intensity; + } + + @Override + public boolean isOn() { + return intensity != 0; + } + + @Override + public void setOn(boolean on) { + intensity = on ? 100 : 0; + } + + public void setDimmerId(Long dimmerId) { + this.dimmerId = dimmerId; + super.setSwitchId(null); + }; + + @Override + public void setSwitchId(Long switchId) { + super.setSwitchId(switchId); + this.dimmerId = null; } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableLightRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableLightRepository.java index 484084b..a32b3c6 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableLightRepository.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableLightRepository.java @@ -1,3 +1,3 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; -public interface DimmableLightRepository extends DeviceRepository {} +public interface DimmableLightRepository extends SwitchableRepository {} 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 2658b35..af00025 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 @@ -1,8 +1,10 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; +import java.util.Set; import javax.persistence.Entity; import javax.persistence.Inheritance; import javax.persistence.InheritanceType; +import javax.persistence.OneToMany; /** Represents a generic dimmer input device */ @Entity @@ -11,4 +13,17 @@ public abstract class Dimmer extends InputDevice { public Dimmer(String kind) { super(kind); } + + @OneToMany(mappedBy = "dimmer") + private Set lights; + + /** + * Get the lights connected to this dimmer + * + * @return duh + */ + @Override + public Set getOutputs() { + return this.lights; + } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/InputDevice.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/InputDevice.java index e632178..da45b67 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/InputDevice.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/InputDevice.java @@ -1,14 +1,22 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; +import java.util.Set; import javax.persistence.Entity; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; /** * A generic abstraction for an input device, i.e. something that captures input either from the * environment (sensor) or the user (switch / dimmer). */ @Entity +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public abstract class InputDevice extends Device { public InputDevice(String kind) { super(kind, FlowType.INPUT); } + + public Set getOutputs() { + return Set.of(); + } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/JWTUserDetailsService.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/JWTUserDetailsService.java index b20832b..06ee415 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/JWTUserDetailsService.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/JWTUserDetailsService.java @@ -15,7 +15,7 @@ public class JWTUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User toReturn = repository.findByUsername(username); - if (toReturn != null) { + if (toReturn != null && toReturn.getEnabled()) { return new org.springframework.security.core.userdetails.User( toReturn.getUsername(), toReturn.getPassword(), Set.of()); } else { 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 0a90998..ce3745c 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 @@ -1,8 +1,6 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; -import java.util.Set; import javax.persistence.Entity; -import javax.persistence.OneToMany; /** * Represents a dimmer able to set absolute intensity values (i.e. knowing the absolute intensity @@ -10,62 +8,19 @@ import javax.persistence.OneToMany; */ @Entity public class KnobDimmer extends Dimmer { + public KnobDimmer() { super("knob-dimmer"); } - @OneToMany(mappedBy = "dimmer") - private Set lights; - /** - * Increases or decreases the current intensity level by 5, moving between absolute multiples of - * 5 between 0 and 100, of all dimmable lights mapped to this knob + * Sets absolutely the intensity level of all lights connected * - * @param inc The direction the knob is turned with + * @param intensity the intensity (must be from 0 to 100) */ - public void modifyIntensity(boolean inc) { - - for (DimmableLight dl : lights) { - int remainder = dl.getIntensity() / 5; - - if (inc) { - dl.setIntensity(dl.getIntensity() - remainder); - dl.setIntensity((dl.getIntensity() + 5) % 105); - } else { - dl.setIntensity(dl.getIntensity() + (5 - remainder)); - dl.setIntensity((dl.getIntensity() - 5) % 105); - } + public void setLightIntensity(int intensity) { + for (DimmableLight dl : getOutputs()) { + dl.setIntensity(intensity); } } - - /** - * Adds a DimmableLight to this set of DimmableLights - * - * @param dl The DimmableLight to be added - */ - public void addLight(DimmableLight dl) { - lights.add(dl); - } - - /** - * Removes the given DimmableLight - * - * @param dl The DimmableLight to be removed - */ - public void removeLight(DimmableLight dl) { - lights.remove(dl); - } - - /** Clears this set */ - public void clearSet() { - lights.clear(); - } - - public void setLights(Set lights) { - this.lights = lights; - } - - public Set getLights() { - return lights; - } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Light.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Light.java deleted file mode 100644 index 68a819b..0000000 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Light.java +++ /dev/null @@ -1,31 +0,0 @@ -package ch.usi.inf.sa4.sanmarinoes.smarthut.models; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Inheritance; -import javax.persistence.InheritanceType; -import javax.validation.constraints.NotNull; - -/** Represents a generic light */ -@Entity -@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) -public abstract class Light extends OutputDevice { - - /** Whether the light is on or not */ - @Column(name = "light_on", nullable = false) - @NotNull - boolean on; - - protected Light(String kind) { - super(kind); - this.on = false; - } - - public boolean isOn() { - return on; - } - - public void setOn(boolean on) { - this.on = on; - } -} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/OutputDevice.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/OutputDevice.java index 39e5dd0..c5b401f 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/OutputDevice.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/OutputDevice.java @@ -1,8 +1,6 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; -import javax.persistence.Entity; -import javax.persistence.Inheritance; -import javax.persistence.InheritanceType; +import javax.persistence.*; /** * Represents a generic output device, i.e. something that causes some behaviour (light, smartPlugs, 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 8f07377..de9c10a 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 @@ -1,11 +1,30 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; +import javax.persistence.Column; import javax.persistence.Entity; +import javax.validation.constraints.NotNull; /** Represents a standard non-dimmable light */ @Entity -public class RegularLight extends Light { +public class RegularLight extends Switchable { + + /** Whether the light is on or not */ + @Column(name = "light_on", nullable = false) + @NotNull + boolean on; + public RegularLight() { super("regular-light"); + this.on = false; + } + + @Override + public boolean isOn() { + return on; + } + + @Override + public void setOn(boolean on) { + this.on = on; } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RegularLightRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RegularLightRepository.java index 43c4e17..cad8831 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RegularLightRepository.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RegularLightRepository.java @@ -1,3 +1,3 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; -public interface RegularLightRepository extends DeviceRepository {} +public interface RegularLightRepository extends SwitchableRepository {} 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 f6edfd3..e54b462 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 @@ -1,5 +1,6 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; +import com.google.gson.annotations.SerializedName; import io.swagger.annotations.ApiModelProperty; import javax.persistence.*; import javax.validation.constraints.NotNull; @@ -8,25 +9,124 @@ import javax.validation.constraints.NotNull; @Entity public class Room { + /** A collection of Semantic UI icons */ + @SuppressWarnings("unused") + public enum Icon { + @SerializedName("home") + HOME("home"), + @SerializedName("coffee") + COFFEE("coffee"), + @SerializedName("beer") + BEER("beer"), + @SerializedName("glass martini") + GLASS_MARTINI("glass martini"), + @SerializedName("film") + FILM("film"), + @SerializedName("video") + VIDEO("video"), + @SerializedName("music") + MUSIC("music"), + @SerializedName("headphones") + HEADPHONES("headphones"), + @SerializedName("fax") + FAX("fax"), + @SerializedName("phone") + PHONE("phone"), + @SerializedName("laptop") + LAPTOP("laptop"), + @SerializedName("bath") + BATH("bath"), + @SerializedName("shower") + SHOWER("shower"), + @SerializedName("bed") + BED("bed"), + @SerializedName("child") + CHILD("child"), + @SerializedName("warehouse") + WAREHOUSE("warehouse"), + @SerializedName("car") + CAR("car"), + @SerializedName("bicycle") + BICYCLE("bicycle"), + @SerializedName("motorcycle") + MOTORCYCLE("motorcycle"), + @SerializedName("archive") + ARCHIVE("archive"), + @SerializedName("boxes") + BOXES("boxes"), + @SerializedName("cubes") + CUBES("cubes"), + @SerializedName("chess") + CHESS("chess"), + @SerializedName("gamepad") + GAMEPAD("gamepad"), + @SerializedName("futbol") + FUTBOL("futbol"), + @SerializedName("table tennis") + TABLE_TENNIS("table tennis"), + @SerializedName("server") + SERVER("server"), + @SerializedName("tv") + TV("tv"), + @SerializedName("heart") + HEART("heart"), + @SerializedName("camera") + CAMERA("camera"), + @SerializedName("trophy") + TROPHY("trophy"), + @SerializedName("wrench") + WRENCH("wrench"), + @SerializedName("image") + IMAGE("image"), + @SerializedName("book") + BOOK("book"), + @SerializedName("university") + UNIVERSITY("university"), + @SerializedName("medkit") + MEDKIT("medkit"), + @SerializedName("paw") + PAW("paw"), + @SerializedName("tree") + TREE("tree"), + @SerializedName("utensils") + UTENSILS("utensils"), + @SerializedName("male") + MALE("male"), + @SerializedName("female") + FEMALE("female"), + @SerializedName("life ring outline") + LIFE_RING_OUTLINE("life ring outline"); + + private String iconName; + + Icon(String s) { + this.iconName = s; + } + + @Override + public String toString() { + return iconName; + } + } + @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id", updatable = false, nullable = false, unique = true) @ApiModelProperty(hidden = true) private Long id; + /** The room icon, out of a set of Semantic UI icons */ + @Column private Icon icon; + /** - * Icon and image are to be given as byte[]. In order to get an encoded string from it, the + * Image is to be given as byte[]. In order to get an encoded string from it, the * Base64.getEncoder().encodeToString(byte[] content) should be used. For further information: * https://www.baeldung.com/java-base64-image-string * https://docs.oracle.com/javase/8/docs/api/java/util/Base64.html */ - @Lob - @Column(name = "icon", columnDefinition = "TEXT") - private byte[] icon; - @Lob @Column(name = "image", columnDefinition = "TEXT") - private byte[] image; + private String image; /** * User that owns the house this room is in as a foreign key id. To use when updating and @@ -65,19 +165,19 @@ public class Room { this.name = name; } - public byte[] getIcon() { + public Icon getIcon() { return icon; } - public void setIcon(byte[] icon) { + public void setIcon(Icon icon) { this.icon = icon; } - public byte[] getImage() { + public String getImage() { return image; } - public void setImage(byte[] image) { + public void setImage(String image) { this.image = image; } 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 e352a52..b1d98a2 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 @@ -42,6 +42,8 @@ public class Sensor extends InputDevice { public void setSensor(SensorType sensor) { this.sensor = sensor; + + // TODO: setup hook for sockets live update } public int getValue() { 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 e0d7981..fe936b3 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 @@ -6,17 +6,19 @@ import javax.validation.constraints.NotNull; /** A smart plug that can be turned either on or off */ @Entity -public class SmartPlug extends OutputDevice { +public class SmartPlug extends Switchable { /** Whether the smart plug is on */ @Column(name = "smart_plug_on", nullable = false) @NotNull private boolean on; + @Override public boolean isOn() { return on; } + @Override public void setOn(boolean on) { this.on = on; } 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 0b2fd344..08d145d 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,3 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; -public interface SmartPlugRepository extends DeviceRepository {} +public interface SmartPlugRepository extends SwitchableRepository {} 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 2576d38..d819dfe 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 @@ -1,12 +1,18 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; +import java.util.HashSet; +import java.util.Set; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.OneToMany; /** A switch input device */ @Entity public class Switch extends InputDevice { + @OneToMany(mappedBy = "switchDevice") + private Set switchables = new HashSet<>(); + /** The state of this switch */ @Column(nullable = false, name = "switch_on") private boolean on; @@ -22,6 +28,15 @@ public class Switch extends InputDevice { */ public void setOn(boolean state) { on = state; + + for (final Switchable s : switchables) { + s.setOn(on); + } + } + + /** Toggle between on and off state */ + public void toggle() { + setOn(!isOn()); } /** @@ -32,4 +47,8 @@ public class Switch extends InputDevice { public boolean isOn() { return on; } + + public Set getOutputs() { + return switchables; + } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Switchable.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Switchable.java new file mode 100644 index 0000000..5ba0702 --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Switchable.java @@ -0,0 +1,47 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.models; + +import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude; +import javax.persistence.*; + +/** A device that can be turned either on or off */ +@Entity +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +public abstract class Switchable extends OutputDevice { + + public static final Connector SWITCH_SWITCHABLE_CONNECTOR = + Connector.basic(Switch::getOutputs, Switchable::setSwitchId); + + @ManyToOne + @GsonExclude + @JoinColumn(name = "switch_id", updatable = false, insertable = false) + private Switch switchDevice; + + @Column(name = "switch_id") + private Long switchId; + + protected Switchable(String kind) { + super(kind); + } + + /** + * Returns whether the device is on (true) or not (false) + * + * @return whether the device is on (true) or not (false) + */ + public abstract boolean isOn(); + + /** + * Sets the on status of the device + * + * @param on the new on status: true for on, false for off + */ + public abstract void setOn(boolean on); + + public Long getSwitchId() { + return switchId; + } + + public void setSwitchId(Long switchId) { + this.switchId = switchId; + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SwitchableRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SwitchableRepository.java new file mode 100644 index 0000000..589542d --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SwitchableRepository.java @@ -0,0 +1,7 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.models; + +/** + * SwitchableRepository acts as a superclass for the other repositories so to mirror in the database + * the class inheritance present among the various switchable devices. + */ +public interface SwitchableRepository extends DeviceRepository {} 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 e33b130..f1b88ca 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 @@ -2,11 +2,6 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; import io.swagger.annotations.ApiModelProperty; import javax.persistence.*; -import javax.validation.constraints.Email; -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Pattern; -import javax.validation.constraints.Size; /** A user of the Smarthut application */ @Entity(name = "smarthutuser") @@ -19,25 +14,15 @@ public class User { private Long id; /** The full name of the user */ - @NotNull @Column(nullable = false) - @NotEmpty(message = "Please provide a full name") private String name; /** The full username of the user */ - @NotNull @Column(nullable = false, unique = true) - @NotEmpty(message = "Please provide a username") private String username; /** A properly salted way to store the password */ - @NotNull @Column(nullable = false) - @NotEmpty(message = "Please provide a password") - @Size( - min = 6, - max = 255, - message = "Your password should be at least 6 characters long and up to 255 chars long") private String password; /** @@ -45,10 +30,6 @@ public class User { * , technically not RFC 5322 compliant */ @Column(nullable = false, unique = true) - @NotNull - @NotEmpty(message = "Please provide an email") - @Email(message = "Please provide a valid email address") - @Pattern(regexp = ".+@.+\\..+", message = "Please provide a valid email address") private String email; @Column(nullable = false) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 9bfe2a7..f760ec8 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -22,4 +22,12 @@ 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 \ No newline at end of file +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 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 new file mode 100644 index 0000000..60761cd --- /dev/null +++ b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/AuthenticationTests.java @@ -0,0 +1,233 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut; + +import static org.assertj.core.api.Assertions.assertThat; + +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.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 com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +@AutoConfigureMockMvc +public class AuthenticationTests extends SmartHutTest { + + @Autowired private TestRestTemplate restTemplate; + + private UserRegistrationRequest getDisabledUser() { + final UserRegistrationRequest disabledUser = new UserRegistrationRequest(); + disabledUser.setName("Disabled User"); + disabledUser.setEmail("disabled@example.com"); + disabledUser.setUsername("disabled"); + disabledUser.setPassword("password"); + 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 = + this.restTemplate.postForEntity( + 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 + } + + @Test + public void registrationShouldReturnBadRequestWithIncorrectFields() { + final Map badJSON = Map.of("luciano", "goretti", "danilo", "malusa"); + + assertThat( + this.restTemplate + .postForEntity(url("/register"), badJSON, JWTResponse.class) + .getStatusCode() + .equals(HttpStatus.BAD_REQUEST)); + } + + @Test + public void registrationShouldReturnBadRequestWithShortPassword() { + final UserRegistrationRequest request = new UserRegistrationRequest(); + request.setName("Mario Goretti"); + request.setEmail("test@example.com"); + request.setUsername("mgo"); + request.setPassword("passw"); + + final ResponseEntity res = + this.restTemplate.postForEntity(url("/register"), request, JsonObject.class); + assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST)); + assertThat(res.getBody() != null); + + final JsonArray errors = res.getBody().getAsJsonArray("errors"); + assertThat(errors.size() == 1); + assertThat(errors.get(0).getAsJsonObject().get("field").getAsString().equals("password")); + } + + @Test + public void registrationShouldReturnBadRequestWithWrongEmail() { + final UserRegistrationRequest request = new UserRegistrationRequest(); + request.setName("Mario Goretti"); + request.setEmail("test@example"); + request.setUsername("mgo"); + request.setPassword("password"); + + final ResponseEntity res = + this.restTemplate.postForEntity(url("/register"), request, JsonObject.class); + assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST)); + assertThat(res.getBody() != null); + + final JsonArray errors = res.getBody().getAsJsonArray("errors"); + assertThat(errors.size() == 1); + assertThat(errors.get(0).getAsJsonObject().get("field").getAsString().equals("email")); + } + + @Test + public void registrationShouldReturnBadRequestWithNoName() { + final UserRegistrationRequest request = new UserRegistrationRequest(); + request.setEmail("test@example.com"); + request.setUsername("mgo"); + request.setPassword("password"); + + final ResponseEntity res = + this.restTemplate.postForEntity(url("/register"), request, JsonObject.class); + assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST)); + assertThat(res.getBody() != null); + + final JsonArray errors = res.getBody().getAsJsonArray("errors"); + assertThat(errors.size() == 1); + assertThat(errors.get(0).getAsJsonObject().get("field").getAsString().equals("name")); + } + + @Test + public void registrationShouldReturnBadRequestWithNoUsername() { + final UserRegistrationRequest request = new UserRegistrationRequest(); + request.setName("Mario Goretti"); + request.setEmail("test@example.com"); + request.setPassword("password"); + + final ResponseEntity res = + this.restTemplate.postForEntity(url("/register"), request, JsonObject.class); + assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST)); + assertThat(res.getBody() != null); + + final JsonArray errors = res.getBody().getAsJsonArray("errors"); + assertThat(errors.size() == 1); + assertThat(errors.get(0).getAsJsonObject().get("field").getAsString().equals("username")); + } + + @Test + public void registrationShouldReturnBadRequestWithDuplicateData() { + { + final ResponseEntity res = + this.restTemplate.postForEntity( + url("/register"), + getDisabledUser(), + DuplicateRegistrationException.class); + assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST)); + assertThat(res.getBody() != null); + } + + { + final UserRegistrationRequest disabledUserDifferentMail = getDisabledUser(); + enabledUser.setEmail("another@example.com"); + + final ResponseEntity res = + this.restTemplate.postForEntity( + url("/register"), + disabledUserDifferentMail, + DuplicateRegistrationException.class); + assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST)); + assertThat(res.getBody() != null); + } + + { + final UserRegistrationRequest disabledUserDifferentUsername = getDisabledUser(); + enabledUser.setUsername("another"); + + final ResponseEntity res = + this.restTemplate.postForEntity( + url("/register"), + disabledUserDifferentUsername, + DuplicateRegistrationException.class); + assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST)); + assertThat(res.getBody() != null); + } + } + + @Test + public void registrationShouldReturnOkWithCorrectData() { + final UserRegistrationRequest request = new UserRegistrationRequest(); + request.setName("Registration Test"); + request.setUsername("smarthut"); + request.setEmail("smarthut.sm@example.com"); + request.setPassword("password"); + + final ResponseEntity res = + this.restTemplate.postForEntity(url("/register"), request, OkResponse.class); + assertThat(res.getStatusCode().equals(HttpStatus.OK)); + assertThat(res.getBody() != null); + } + + @Test + public void loginShouldReturnBadRequestWithIncorrectFields() { + final Map badJSON = Map.of("badkey", 3, "password", "ciaomamma"); + + assertThat( + this.restTemplate + .postForEntity(url("/auth/login"), badJSON, JWTResponse.class) + .getStatusCode() + .equals(HttpStatus.BAD_REQUEST)); + } + + @Test + public void loginShouldReturnUnauthorizedWithNonExistantUser() { + final JWTRequest request = new JWTRequest(); + request.setUsernameOrEmail("roberto"); + request.setPassword("ciaomamma"); + + final ResponseEntity res = + this.restTemplate.postForEntity( + url("/auth/login"), request, UnauthorizedException.class); + assertThat(res.getStatusCode().equals(HttpStatus.UNAUTHORIZED)); + assertThat(res.getBody() != null); + assertThat(!res.getBody().isUserDisabled()); + } + + @Test + public void loginShouldReturnUnauthorizedWithDisabledUser() { + final JWTRequest request = new JWTRequest(); + request.setUsernameOrEmail("disabled"); + request.setPassword("password"); + + final ResponseEntity res = + this.restTemplate.postForEntity( + url("/auth/login"), request, UnauthorizedException.class); + assertThat(res.getStatusCode().equals(HttpStatus.UNAUTHORIZED)); + assertThat(res.getBody() != null); + assertThat(res.getBody().isUserDisabled()); + } +} 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 new file mode 100644 index 0000000..5c6e097 --- /dev/null +++ b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmartHutTest.java @@ -0,0 +1,25 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut; + +import org.junit.jupiter.api.BeforeEach; + +public abstract class SmartHutTest { + private boolean setupDone = false; + + protected final String getBaseURL() { + return "http://localhost:2000/"; + } + + protected final String url(final String url) { + return getBaseURL() + url; + } + + protected void setUp() {} + + @BeforeEach + void setUpHack() { + if (!setupDone) { + setUp(); + setupDone = true; + } + } +} diff --git a/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmarthutApplicationTests.java b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmarthutApplicationTests.java index 5f1f8fd..dbd7e21 100644 --- a/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmarthutApplicationTests.java +++ b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmarthutApplicationTests.java @@ -1,11 +1,26 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; +import static org.assertj.core.api.Assertions.assertThat; -@SpringBootTest -class SmarthutApplicationTests { +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +@AutoConfigureMockMvc +public class SmarthutApplicationTests extends SmartHutTest { + + @Autowired private TestRestTemplate restTemplate; @Test - void contextLoads() {} + public void anonymousGreetingShouldNotBeAuthorized() throws Exception { + assertThat( + this.restTemplate + .getForEntity(getBaseURL(), Void.class) + .getStatusCode() + .equals(HttpStatus.UNAUTHORIZED)); + } } diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 0000000..ce6fe39 --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,35 @@ +spring.http.converters.preferred-json-mapper=gson +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1 +spring.datasource.username=sa +spring.datasource.password=sa + +# Hibernate properties +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 + +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.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