Merge branch 'dev' of lab.si.usi.ch:sa4-2020/the-sanmarinoes/backend into 51-users-can-add-smart-curtains-blinds

# Conflicts:
#	.gitlab-ci.yml
This commit is contained in:
Jacob Salvi 2020-04-09 14:27:12 +02:00
commit 7f34e2b391
49 changed files with 572 additions and 198 deletions

2
.gitignore vendored
View file

@ -1,5 +1,7 @@
.idea/** .idea/**
data/**/*
**/.DS_Store **/.DS_Store
# Compiled class file # Compiled class file

View file

@ -1,8 +1,9 @@
FROM openjdk:13-jdk-alpine FROM openjdk:13-jdk-alpine
ENV DB_URL ""
ENV DB_USER ""
ENV DB_PASS ""
ARG JAR_FILE=build/libs/smarthut*.jar ARG JAR_FILE=build/libs/smarthut*.jar
COPY ${JAR_FILE} app.jar COPY ${JAR_FILE} app.jar
ENV SMARTHUT_THIS_VALUE_IS_PROD_IF_THIS_IS_A_CONTAINER_PIZZOCCHERI=prod
EXPOSE 8080 EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"] ENTRYPOINT ["java","-jar","/app.jar"]

13
HELP.md
View file

@ -1,13 +0,0 @@
# Getting Started
### Reference Documentation
For further reference, please consider the following sections:
* [Official Gradle documentation](https://docs.gradle.org)
* [Spring Boot Gradle Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.2.4.RELEASE/gradle-plugin/reference/html/)
### Additional Links
These additional references should also help you:
* [Gradle Build Scans insights for your project's build](https://scans.gradle.com#gradle)

View file

@ -1,2 +1,53 @@
# backend # backend
## Installation guide
In order to install a SmartHut.sm, you can use *Docker* and *Docker Compose*
in order to create che corresponding containers.
Use the following `docker-compose.yml` example file. Change the values
of `$PASSWORD` and `$SECRET` to respectively the chosen PostgreSQL password
and the JWT secret used to run the server. `$SECRET` must be at least 64 chars long.
```yaml
version: '3'
services:
smarthutdb:
restart: always
image: postgres:12-alpine
container_name: smarthutdb
volumes:
- ./data:/var/lib/postgresql/data
environment:
PGDATA: /var/lib/postgresql/data/data
POSTGRES_DB: smarthut
POSTGRES_USERNAME: postgres
POSTGRES_PASSWORD: $PASSWORD
smarthutbackend:
restart: always
image: smarthutsm/smarthut-backend:M1
ports:
- 8080:8080
environment:
- POSTGRES_JDBC=jdbc:postgresql://smarthutdb:5432/smarthut
- POSTGRES_USER=postgres
- POSTGRES_PASS=$PASSWORD
- SECRET=$SECRET
- MAIL_HOST=smtp.gmail.com
- MAIL_PORT=587
- MAIL_STARTTLS=true
- MAIL_USER=smarthut.sm@gmail.com
- MAIL_PASS=dcadvbagqfkwbfts
- BACKEND_URL=http://localhost:8080
- FRONTEND_URL=http://localhost
smarthut:
restart: always
image: smarthutsm/smarthut:M1
ports:
- 80:80
environment:
- BACKEND_URL=http://localhost:8080
```

View file

@ -4,21 +4,16 @@ plugins {
id "de.aaschmid.cpd" version "3.1" id "de.aaschmid.cpd" version "3.1"
id 'java' id 'java'
} }
group = 'ch.usi.inf.sa4.sanmarinoes' group = 'ch.usi.inf.sa4.sanmarinoes'
version = '0.0.1-SNAPSHOT' version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11' sourceCompatibility = '11'
repositories { repositories {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
compile 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' compile 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final'
compile "org.springframework.boot:spring-boot-starter-websocket" compile "org.springframework.boot:spring-boot-starter-websocket"
implementation 'org.springframework.boot:spring-boot-starter' implementation 'org.springframework.boot:spring-boot-starter'
implementation 'com.sun.mail:javax.mail:1.6.2' 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-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-security'
@ -32,22 +27,18 @@ dependencies {
compile 'io.springfox:springfox-swagger2:2.9.2' compile 'io.springfox:springfox-swagger2:2.9.2'
compile 'io.springfox:springfox-swagger-ui:2.9.2' compile 'io.springfox:springfox-swagger-ui:2.9.2'
compile 'org.springframework.boot:spring-boot-configuration-processor' compile 'org.springframework.boot:spring-boot-configuration-processor'
testCompile 'org.springframework.boot:spring-boot-starter-webflux'
implementation('org.springframework.boot:spring-boot-starter-web') { implementation('org.springframework.boot:spring-boot-starter-web') {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-json' exclude group: 'org.springframework.boot', module: 'spring-boot-starter-json'
} }
testImplementation('org.springframework.boot:spring-boot-starter-test') { testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
} }
testImplementation 'org.springframework.security:spring-security-test' testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'com.h2database:h2:1.4.200' testImplementation 'com.h2database:h2:1.4.200'
// Fixes https://stackoverflow.com/a/60455550 // Fixes https://stackoverflow.com/a/60455550
testImplementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.11' testImplementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.11'
} }
test { test {
useJUnitPlatform() useJUnitPlatform()
} }

View file

@ -1,7 +1,6 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.config; package ch.usi.inf.sa4.sanmarinoes.smarthut.config;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import javax.servlet.*; import javax.servlet.*;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -14,23 +13,13 @@ import org.springframework.stereotype.Component;
@Component @Component
public class CORSFilter implements Filter { public class CORSFilter implements Filter {
static void setCORSHeaders(HttpServletResponse response) { public static void setCORSHeaders(HttpServletResponse response) {
response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "HEAD, PUT, POST, GET, OPTIONS, DELETE"); response.setHeader("Access-Control-Allow-Methods", "*");
response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "*");
response.setHeader( response.setHeader("Access-Control-Allow-Credentials", "true");
"Access-Control-Allow-Headers", response.setHeader("Access-Control-Expose-Headers", "*");
String.join( response.setHeader("Access-Control-Max-Age", "6".repeat(99));
",",
List.of(
"Access-Control-Allow-Headers",
"Origin",
"Accept",
"X-Requested-With",
"Authorization",
"Content-Type",
"Access-Control-Request-Method",
"Access-Control-Request-Headers")));
} }
@Override @Override

View file

@ -30,17 +30,21 @@ public class EmailConfigurationService {
*/ */
@NotNull private String registrationPath; @NotNull private String registrationPath;
/**
* The URL to follow for password reset email confirmation. Has to end with the start of a query
* parameter
*/
@NotNull private String resetPasswordPath;
/** The email subject for a reset password email */ /** The email subject for a reset password email */
@NotNull private String resetPasswordSubject; @NotNull private String resetPasswordSubject;
/** The text in the email body preceding the confirmation URL for a reset password email */ /** The text in the email body preceding the confirmation URL for a reset password email */
@NotNull private String resetPassword; @NotNull private String resetPassword;
/** @NotNull private String resetPasswordRedirect;
* The URL to follow for password reset email confirmation. Has to end with the start of a query
* parameter @NotNull private String registrationRedirect;
*/
@NotNull private String resetPasswordPath;
public String getRegistrationSubject() { public String getRegistrationSubject() {
return registrationSubject; return registrationSubject;
@ -89,4 +93,20 @@ public class EmailConfigurationService {
public void setResetPasswordPath(String resetPasswordPath) { public void setResetPasswordPath(String resetPasswordPath) {
this.resetPasswordPath = resetPasswordPath; this.resetPasswordPath = resetPasswordPath;
} }
public String getResetPasswordRedirect() {
return resetPasswordRedirect;
}
public void setResetPasswordRedirect(String resetPasswordRedirect) {
this.resetPasswordRedirect = resetPasswordRedirect;
}
public String getRegistrationRedirect() {
return registrationRedirect;
}
public void setRegistrationRedirect(String registrationRedirect) {
this.registrationRedirect = registrationRedirect;
}
} }

View file

@ -1,6 +1,5 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.config; package ch.usi.inf.sa4.sanmarinoes.smarthut.config;
import java.util.List; import java.util.List;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -75,7 +74,8 @@ public class SpringFoxConfig {
.or(PathSelectors.regex("/sensor.*")::apply) .or(PathSelectors.regex("/sensor.*")::apply)
.or(PathSelectors.regex("/smartPlug.*")::apply) .or(PathSelectors.regex("/smartPlug.*")::apply)
.or(PathSelectors.regex("/switch.*")::apply) .or(PathSelectors.regex("/switch.*")::apply)
.or(PathSelectors.regex("/motionSensor.*")::apply); .or(PathSelectors.regex("/motionSensor.*")::apply)
.or(PathSelectors.regex("/auth/profile.*")::apply);
} }
/** /**

View file

@ -3,11 +3,9 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.JWTTokenUtils; import ch.usi.inf.sa4.sanmarinoes.smarthut.config.JWTTokenUtils;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.JWTRequest; 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.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.UnauthorizedException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UserNotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UserNotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
import io.swagger.annotations.Authorization;
import java.security.Principal; import java.security.Principal;
import javax.validation.Valid; import javax.validation.Valid;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
@ -72,19 +70,9 @@ public class AuthenticationController {
return new JWTResponse(token); return new JWTResponse(token);
} }
@Authorization(value = "Bearer") @GetMapping("/profile")
@PatchMapping("/update") public User profile(final Principal principal) {
public User update( return userRepository.findByUsername(principal.getName());
@Valid @RequestBody final UserUpdateRequest userData, final Principal principal) {
final User oldUser = userRepository.findByUsername(principal.getName());
if (userData.getName() != null) oldUser.setName(userData.getName());
if (userData.getEmail() != null) {
oldUser.setEmail(userData.getEmail());
// TODO: handle email verification
}
if (userData.getPassword() != null)
oldUser.setPassword(encoder.encode(userData.getPassword()));
return userRepository.save(oldUser);
} }
private void authenticate(String username, String password) throws UnauthorizedException { private void authenticate(String username, String password) throws UnauthorizedException {

View file

@ -6,6 +6,7 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.ButtonDimmerDimRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.GenericDeviceSaveReguest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.GenericDeviceSaveReguest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
import java.security.Principal;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import javax.validation.Valid; import javax.validation.Valid;
@ -52,10 +53,13 @@ public class ButtonDimmerController
} }
@PutMapping("/dim") @PutMapping("/dim")
public Set<DimmableLight> dim(@Valid @RequestBody final ButtonDimmerDimRequest bd) public Set<DimmableLight> dim(
@Valid @RequestBody final ButtonDimmerDimRequest bd, final Principal principal)
throws NotFoundException { throws NotFoundException {
final ButtonDimmer buttonDimmer = final ButtonDimmer buttonDimmer =
buttonDimmerRepository.findById(bd.getId()).orElseThrow(NotFoundException::new); buttonDimmerRepository
.findByIdAndUsername(bd.getId(), principal.getName())
.orElseThrow(NotFoundException::new);
switch (bd.getDimType()) { switch (bd.getDimType()) {
case UP: case UP:

View file

@ -27,16 +27,17 @@ public class DeviceController {
} }
@PutMapping @PutMapping
public Device update(@Valid @RequestBody DeviceSaveRequest deviceSaveRequest) public Device update(
@Valid @RequestBody DeviceSaveRequest deviceSaveRequest, final Principal principal)
throws NotFoundException, BadDataException { throws NotFoundException, BadDataException {
final Device d = final Device d =
deviceRepository deviceRepository
.findById(deviceSaveRequest.getId()) .findByIdAndUsername(deviceSaveRequest.getId(), principal.getName())
.orElseThrow(NotFoundException::new); .orElseThrow(NotFoundException::new);
// check if roomId is valid // check if roomId is valid
roomRepository roomRepository
.findById(deviceSaveRequest.getRoomId()) .findByIdAndUsername(deviceSaveRequest.getRoomId(), principal.getName())
.orElseThrow(() -> new BadDataException("roomId is not a valid room id")); .orElseThrow(() -> new BadDataException("roomId is not a valid room id"));
d.setRoomId(deviceSaveRequest.getRoomId()); d.setRoomId(deviceSaveRequest.getRoomId());

View file

@ -6,6 +6,7 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.DimmableLightSaveRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DimmableLight; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DimmableLight;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DimmableLightRepository; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DimmableLightRepository;
import java.security.Principal;
import java.util.List; import java.util.List;
import javax.validation.Valid; import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -43,10 +44,14 @@ public class DimmableLightController {
} }
@PutMapping @PutMapping
public DimmableLight update(@Valid @RequestBody DimmableLightSaveRequest sp) public DimmableLight update(
@Valid @RequestBody DimmableLightSaveRequest sp, final Principal principal)
throws NotFoundException { throws NotFoundException {
return save( return save(
dimmableLightService.findById(sp.getId()).orElseThrow(NotFoundException::new), sp); dimmableLightService
.findByIdAndUsername(sp.getId(), principal.getName())
.orElseThrow(NotFoundException::new),
sp);
} }
@DeleteMapping("/{id}") @DeleteMapping("/{id}")

View file

@ -6,6 +6,7 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.GenericDeviceSaveReguest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.KnobDimmerDimRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.KnobDimmerDimRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
import java.security.Principal;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import javax.validation.Valid; import javax.validation.Valid;
@ -53,10 +54,13 @@ public class KnobDimmerController
} }
@PutMapping("/dimTo") @PutMapping("/dimTo")
public Set<DimmableLight> dimTo(@Valid @RequestBody final KnobDimmerDimRequest bd) public Set<DimmableLight> dimTo(
@Valid @RequestBody final KnobDimmerDimRequest bd, final Principal principal)
throws NotFoundException { throws NotFoundException {
final KnobDimmer dimmer = final KnobDimmer dimmer =
knobDimmerRepository.findById(bd.getId()).orElseThrow(NotFoundException::new); knobDimmerRepository
.findByIdAndUsername(bd.getId(), principal.getName())
.orElseThrow(NotFoundException::new);
dimmer.setLightIntensity(bd.getIntensity()); dimmer.setLightIntensity(bd.getIntensity());
dimmableLightRepository.saveAll(dimmer.getOutputs()); dimmableLightRepository.saveAll(dimmer.getOutputs());

View file

@ -6,6 +6,7 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.RegularLightSaveRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.RegularLight; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.RegularLight;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.RegularLightRepository; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.RegularLightRepository;
import java.security.Principal;
import java.util.List; import java.util.List;
import javax.validation.Valid; import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -50,10 +51,14 @@ public class RegularLightController {
} }
@PutMapping @PutMapping
public RegularLight update(@Valid @RequestBody RegularLightSaveRequest rl) public RegularLight update(
@Valid @RequestBody RegularLightSaveRequest rl, final Principal principal)
throws NotFoundException { throws NotFoundException {
return save( return save(
regularLightService.findById(rl.getId()).orElseThrow(NotFoundException::new), rl); regularLightService
.findByIdAndUsername(rl.getId(), principal.getName())
.orElseThrow(NotFoundException::new),
rl);
} }
@DeleteMapping("/{id}") @DeleteMapping("/{id}")

View file

@ -23,6 +23,12 @@ public class RoomController {
@Autowired private DeviceRepository<Device> deviceRepository; @Autowired private DeviceRepository<Device> deviceRepository;
@Autowired private SwitchRepository switchRepository;
@Autowired private ButtonDimmerRepository buttonDimmerRepository;
@Autowired private KnobDimmerRepository knobDimmerRepository;
@GetMapping @GetMapping
public List<Room> findAll() { public List<Room> findAll() {
return toList(roomRepository.findAll()); return toList(roomRepository.findAll());
@ -33,44 +39,57 @@ public class RoomController {
return roomRepository.findById(id).orElseThrow(NotFoundException::new); return roomRepository.findById(id).orElseThrow(NotFoundException::new);
} }
private Room save(final RoomSaveRequest r, final Principal principal, boolean setWhenNull) { @PostMapping
Room newRoom = new Room(); public @ResponseBody Room create(
@Valid @RequestBody RoomSaveRequest r, final Principal principal) {
final String username = principal.getName(); final String username = principal.getName();
final Long userId = userRepository.findByUsername(username).getId(); final Long userId = userRepository.findByUsername(username).getId();
final String img = r.getImage(); final String img = r.getImage();
final Room.Icon icon = r.getIcon(); final Room.Icon icon = r.getIcon();
final Room newRoom = new Room();
newRoom.setUserId(userId); newRoom.setUserId(userId);
newRoom.setName(r.getName()); newRoom.setName(r.getName());
if (img != null) { newRoom.setImage(img);
newRoom.setImage(img); newRoom.setIcon(icon);
} else if (setWhenNull) {
newRoom.setImage(null); return roomRepository.save(newRoom);
}
@PutMapping("/{id}")
public @ResponseBody Room update(
@PathVariable("id") long id, @RequestBody RoomSaveRequest r, final Principal principal)
throws NotFoundException {
final Room newRoom =
roomRepository
.findByIdAndUsername(id, principal.getName())
.orElseThrow(NotFoundException::new);
final String img = r.getImage();
final Room.Icon icon = r.getIcon();
if (r.getName() != null) {
newRoom.setName(r.getName());
} }
if ("".equals(img)) {
newRoom.setImage(null);
} else if (img != null) {
newRoom.setImage(img);
}
if (icon != null) { if (icon != null) {
newRoom.setIcon(icon); newRoom.setIcon(icon);
} else if (setWhenNull) {
newRoom.setIcon(null);
} }
return roomRepository.save(newRoom); return roomRepository.save(newRoom);
} }
@PostMapping
public @ResponseBody Room create(
@Valid @RequestBody RoomSaveRequest r, final Principal principal) {
return this.save(r, principal, true);
}
@PutMapping
public @ResponseBody Room update(
@Valid @RequestBody RoomSaveRequest r, final Principal principal) {
return this.save(r, principal, false);
}
@DeleteMapping("/{id}") @DeleteMapping("/{id}")
public void deleteById(@PathVariable("id") long id) { public void deleteById(@PathVariable("id") long id) {
switchRepository.deleteAllByRoomId(id);
knobDimmerRepository.deleteAllByRoomId(id);
buttonDimmerRepository.deleteAllByRoomId(id);
roomRepository.deleteById(id); roomRepository.deleteById(id);
} }
@ -79,7 +98,7 @@ public class RoomController {
* id). * id).
*/ */
@GetMapping(path = "/{roomId}/devices") @GetMapping(path = "/{roomId}/devices")
public @ResponseBody List<? extends Device> getDevices(@PathVariable("roomId") long roomid) { public List<Device> getDevices(@PathVariable("roomId") long roomid) {
return deviceRepository.findByRoomId(roomid); return deviceRepository.findByRoomId(roomid);
} }
} }

View file

@ -5,6 +5,7 @@ import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SmartPlugSaveRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SmartPlugSaveRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
import java.security.Principal;
import java.util.*; import java.util.*;
import java.util.List; import java.util.List;
import javax.validation.Valid; import javax.validation.Valid;
@ -14,7 +15,7 @@ import org.springframework.web.bind.annotation.*;
@RestController @RestController
@EnableAutoConfiguration @EnableAutoConfiguration
@RequestMapping("/smartplug") @RequestMapping("/smartPlug")
public class SmartPlugController { public class SmartPlugController {
@Autowired private SmartPlugRepository smartPlugRepository; @Autowired private SmartPlugRepository smartPlugRepository;
@ -44,9 +45,25 @@ public class SmartPlugController {
} }
@PutMapping @PutMapping
public SmartPlug update(@Valid @RequestBody SmartPlugSaveRequest sp) throws NotFoundException { public SmartPlug update(@Valid @RequestBody SmartPlugSaveRequest sp, final Principal principal)
throws NotFoundException {
return save( return save(
smartPlugRepository.findById(sp.getId()).orElseThrow(NotFoundException::new), sp); smartPlugRepository
.findByIdAndUsername(sp.getId(), principal.getName())
.orElseThrow(NotFoundException::new),
sp);
}
@DeleteMapping("/{id}/meter")
public SmartPlug resetMeter(@PathVariable("id") long id, final Principal principal)
throws NotFoundException {
final SmartPlug s =
smartPlugRepository
.findByIdAndUsername(id, principal.getName())
.orElseThrow(NotFoundException::new);
s.resetTotalConsumption();
return smartPlugRepository.save(s);
} }
@DeleteMapping("/{id}") @DeleteMapping("/{id}")

View file

@ -6,6 +6,7 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.GenericDeviceSaveReguest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SwitchOperationRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SwitchOperationRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
import java.security.Principal;
import java.util.*; import java.util.*;
import java.util.List; import java.util.List;
import javax.validation.Valid; import javax.validation.Valid;
@ -55,9 +56,13 @@ public class SwitchController extends InputDeviceConnectionController<Switch, Sw
} }
@PutMapping("/operate") @PutMapping("/operate")
public Set<Switchable> operate(@Valid @RequestBody final SwitchOperationRequest sr) public Set<Switchable> operate(
@Valid @RequestBody final SwitchOperationRequest sr, final Principal principal)
throws NotFoundException { throws NotFoundException {
final Switch s = switchRepository.findById(sr.getId()).orElseThrow(NotFoundException::new); final Switch s =
switchRepository
.findByIdAndUsername(sr.getId(), principal.getName())
.orElseThrow(NotFoundException::new);
switch (sr.getType()) { switch (sr.getType()) {
case ON: case ON:

View file

@ -13,6 +13,8 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationTokenRepository;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository;
import ch.usi.inf.sa4.sanmarinoes.smarthut.service.EmailSenderService; import ch.usi.inf.sa4.sanmarinoes.smarthut.service.EmailSenderService;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@ -158,8 +160,10 @@ public class UserAccountController {
* @throws EmailTokenNotFoundException if given token is not a valid token for password reset * @throws EmailTokenNotFoundException if given token is not a valid token for password reset
*/ */
@PutMapping("/reset-password") @PutMapping("/reset-password")
public OkResponse resetPassword(@Valid @RequestBody PasswordResetRequest resetRequest) public OkResponse resetPassword(
throws EmailTokenNotFoundException { @Valid @RequestBody PasswordResetRequest resetRequest,
final HttpServletResponse response)
throws EmailTokenNotFoundException, IOException {
final ConfirmationToken token = final ConfirmationToken token =
confirmationTokenRepository.findByConfirmationToken( confirmationTokenRepository.findByConfirmationToken(
resetRequest.getConfirmationToken()); resetRequest.getConfirmationToken());
@ -187,16 +191,17 @@ public class UserAccountController {
* confirmation * confirmation
*/ */
@GetMapping(value = "/confirm-account") @GetMapping(value = "/confirm-account")
public OkResponse confirmUserAccount(@RequestParam("token") @NotNull String confirmationToken) public void confirmUserAccount(
throws EmailTokenNotFoundException { @RequestParam("token") @NotNull String confirmationToken,
final HttpServletResponse response)
throws EmailTokenNotFoundException, IOException {
final ConfirmationToken token = final ConfirmationToken token =
confirmationTokenRepository.findByConfirmationToken(confirmationToken); confirmationTokenRepository.findByConfirmationToken(confirmationToken);
if (token != null && !token.getResetPassword()) { if (token != null && !token.getResetPassword()) {
token.getUser().setEnabled(true); token.getUser().setEnabled(true);
userRepository.save(token.getUser()); userRepository.save(token.getUser());
// TODO: redirect to frontend response.sendRedirect(emailConfig.getRegistrationRedirect());
return new OkResponse();
} else { } else {
throw new EmailTokenNotFoundException(); throw new EmailTokenNotFoundException();
} }

View file

@ -11,7 +11,7 @@ public class DimmableLightSaveRequest {
/** The light intensity value. Goes from 0 (off) to 100 (on) */ /** The light intensity value. Goes from 0 (off) to 100 (on) */
@NotNull @NotNull
@Min(1) @Min(0)
@Max(100) @Max(100)
private Integer intensity = 0; private Integer intensity = 0;

View file

@ -6,6 +6,9 @@ import javax.validation.constraints.NotNull;
public class RoomSaveRequest { public class RoomSaveRequest {
/** Room identifier */
private long id;
@NotNull private Room.Icon icon; @NotNull private Room.Icon icon;
/** /**
@ -19,6 +22,14 @@ public class RoomSaveRequest {
/** The user given name of this room (e.g. 'Master bedroom') */ /** The user given name of this room (e.g. 'Master bedroom') */
@NotNull private String name; @NotNull private String name;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() { public String getName() {
return name; return name;
} }

View file

@ -13,7 +13,7 @@ public class ButtonDimmer extends Dimmer {
private static final int DIM_INCREMENT = 10; private static final int DIM_INCREMENT = 10;
public ButtonDimmer() { public ButtonDimmer() {
super("button-dimmer"); super("buttonDimmer");
} }
/** Increases the current intensity level of the dimmable light by DIM_INCREMENT */ /** Increases the current intensity level of the dimmable light by DIM_INCREMENT */

View file

@ -1,3 +1,9 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models; package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
public interface ButtonDimmerRepository extends DeviceRepository<ButtonDimmer> {} import javax.transaction.Transactional;
public interface ButtonDimmerRepository extends DeviceRepository<ButtonDimmer> {
@Transactional
void deleteAllByRoomId(long roomId);
}

View file

@ -6,6 +6,8 @@ import org.springframework.data.repository.CrudRepository;
public interface ConfirmationTokenRepository extends CrudRepository<ConfirmationToken, String> { public interface ConfirmationTokenRepository extends CrudRepository<ConfirmationToken, String> {
ConfirmationToken findByConfirmationToken(String confirmationToken); ConfirmationToken findByConfirmationToken(String confirmationToken);
ConfirmationToken findByUser(User user);
@Transactional @Transactional
void deleteByUserAndResetPassword(User user, boolean resetPassword); void deleteByUserAndResetPassword(User user, boolean resetPassword);
} }

View file

@ -36,7 +36,7 @@ public abstract class Device {
* The room this device belongs in, as a foreign key id. To use when updating and inserting from * The room this device belongs in, as a foreign key id. To use when updating and inserting from
* a REST call. * a REST call.
*/ */
@Column(name = "room_id", nullable = false, unique = true) @Column(name = "room_id", nullable = false)
@NotNull @NotNull
private Long roomId; private Long roomId;
@ -49,17 +49,13 @@ public abstract class Device {
* The name for the category of this particular device (e.g 'dimmer'). Not stored in the * The name for the category of this particular device (e.g 'dimmer'). Not stored in the
* database but set thanks to constructors * database but set thanks to constructors
*/ */
@ApiModelProperty(hidden = true) @Transient private final String kind;
@Transient
private final String kind;
/** /**
* The way this device behaves in the automation flow. Not stored in the database but set thanks * The way this device behaves in the automation flow. Not stored in the database but set thanks
* to constructors * to constructors
*/ */
@ApiModelProperty(hidden = true) @Transient private final FlowType flowType;
@Transient
private final FlowType flowType;
public long getId() { public long getId() {
return id; return id;

View file

@ -2,6 +2,7 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import javax.transaction.Transactional;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
@ -20,6 +21,7 @@ public interface DeviceRepository<T extends Device> extends CrudRepository<T, Lo
* @param username a User's username * @param username a User's username
* @return an optional device, empty if none found * @return an optional device, empty if none found
*/ */
@Transactional
@Query("SELECT d FROM Device d JOIN d.room r JOIN r.user u WHERE d.id = ?1 AND u.username = ?2") @Query("SELECT d FROM Device d JOIN d.room r JOIN r.user u WHERE d.id = ?1 AND u.username = ?2")
Optional<T> findByIdAndUsername(Long id, String username); Optional<T> findByIdAndUsername(Long id, String username);
@ -29,6 +31,7 @@ public interface DeviceRepository<T extends Device> extends CrudRepository<T, Lo
* @param username the User's username * @param username the User's username
* @return all devices of that user * @return all devices of that user
*/ */
@Transactional
@Query("SELECT d FROM Device d JOIN d.room r JOIN r.user u WHERE u.username = ?1") @Query("SELECT d FROM Device d JOIN d.room r JOIN r.user u WHERE u.username = ?1")
List<T> findAllByUsername(String username); List<T> findAllByUsername(String username);
@ -38,6 +41,7 @@ public interface DeviceRepository<T extends Device> extends CrudRepository<T, Lo
* @param deviceId the device id * @param deviceId the device id
* @return a user object * @return a user object
*/ */
@Transactional
@Query("SELECT u FROM Device d JOIN d.room r JOIN r.user u WHERE d.id = ?1") @Query("SELECT u FROM Device d JOIN d.room r JOIN r.user u WHERE d.id = ?1")
User findUser(Long deviceId); User findUser(Long deviceId);
} }

View file

@ -21,7 +21,7 @@ public class DimmableLight extends Switchable {
Connector.basic(KnobDimmer::getOutputs, DimmableLight::setDimmerId); Connector.basic(KnobDimmer::getOutputs, DimmableLight::setDimmerId);
public DimmableLight() { public DimmableLight() {
super("light"); super("dimmableLight");
} }
@ManyToOne @ManyToOne
@ -39,6 +39,10 @@ public class DimmableLight extends Switchable {
@Max(100) @Max(100)
private Integer intensity = 0; private Integer intensity = 0;
@NotNull
@Column(nullable = false)
private Integer oldIntensity = 100;
public Integer getIntensity() { public Integer getIntensity() {
return intensity; return intensity;
} }
@ -55,8 +59,10 @@ public class DimmableLight extends Switchable {
this.intensity = 0; this.intensity = 0;
} else if (intensity > 100) { } else if (intensity > 100) {
this.intensity = 100; this.intensity = 100;
this.oldIntensity = 100;
} else { } else {
this.intensity = intensity; this.intensity = intensity;
this.oldIntensity = intensity;
} }
} }
@ -67,13 +73,13 @@ public class DimmableLight extends Switchable {
@Override @Override
public void setOn(boolean on) { public void setOn(boolean on) {
intensity = on ? 100 : 0; intensity = on ? oldIntensity : 0;
} }
public void setDimmerId(Long dimmerId) { public void setDimmerId(Long dimmerId) {
this.dimmerId = dimmerId; this.dimmerId = dimmerId;
super.setSwitchId(null); super.setSwitchId(null);
}; }
@Override @Override
public void setSwitchId(Long switchId) { public void setSwitchId(Long switchId) {

View file

@ -6,6 +6,7 @@ import javax.persistence.Entity;
import javax.persistence.Inheritance; import javax.persistence.Inheritance;
import javax.persistence.InheritanceType; import javax.persistence.InheritanceType;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
import javax.persistence.PreRemove;
/** Represents a generic dimmer input device */ /** Represents a generic dimmer input device */
@Entity @Entity
@ -32,4 +33,11 @@ public abstract class Dimmer extends InputDevice {
public void addDimmableLight(DimmableLight dimmableLight) { public void addDimmableLight(DimmableLight dimmableLight) {
lights.add(dimmableLight); lights.add(dimmableLight);
} }
@PreRemove
private void removeLightsFromDimmer() {
for (DimmableLight dl : getOutputs()) {
dl.setDimmerId(null);
}
}
} }

View file

@ -10,7 +10,7 @@ import javax.persistence.Entity;
public class KnobDimmer extends Dimmer { public class KnobDimmer extends Dimmer {
public KnobDimmer() { public KnobDimmer() {
super("knob-dimmer"); super("knobDimmer");
} }
/** /**

View file

@ -1,3 +1,9 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models; package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
public interface KnobDimmerRepository extends DeviceRepository<KnobDimmer> {} import javax.transaction.Transactional;
public interface KnobDimmerRepository extends DeviceRepository<KnobDimmer> {
@Transactional
void deleteAllByRoomId(long roomId);
}

View file

@ -19,6 +19,6 @@ public class MotionSensor extends InputDevice {
} }
public MotionSensor() { public MotionSensor() {
super("motion-sensor"); super("motionSensor");
} }
} }

View file

@ -14,7 +14,7 @@ public class RegularLight extends Switchable {
boolean on; boolean on;
public RegularLight() { public RegularLight() {
super("regular-light"); super("regularLight");
this.on = false; this.on = false;
} }

View file

@ -3,6 +3,8 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude; import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.*; import javax.persistence.*;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@ -125,7 +127,6 @@ public class Room {
* https://www.baeldung.com/java-base64-image-string * https://www.baeldung.com/java-base64-image-string
* https://docs.oracle.com/javase/8/docs/api/java/util/Base64.html * https://docs.oracle.com/javase/8/docs/api/java/util/Base64.html
*/ */
@Lob
@Column(name = "image", columnDefinition = "TEXT") @Column(name = "image", columnDefinition = "TEXT")
private String image; private String image;
@ -134,12 +135,16 @@ public class Room {
@GsonExclude @GsonExclude
private User user; private User user;
@OneToMany(mappedBy = "room", orphanRemoval = true)
@GsonExclude
private Set<Device> devices = new HashSet<>();
/** /**
* User that owns the house this room is in as a foreign key id. To use when updating and * User that owns the house this room is in as a foreign key id. To use when updating and
* inserting from a REST call. * inserting from a REST call.
*/ */
@NotNull @NotNull
@Column(name = "user_id", nullable = false, unique = true) @Column(name = "user_id", nullable = false)
private Long userId; private Long userId;
/** The user given name of this room (e.g. 'Master bedroom') */ /** The user given name of this room (e.g. 'Master bedroom') */
@ -187,6 +192,10 @@ public class Room {
this.image = image; this.image = image;
} }
public Set<Device> getDevices() {
return devices;
}
@Override @Override
public String toString() { public String toString() {
return "Room{" + "id=" + id + ", name='" + name + "\'}"; return "Room{" + "id=" + id + ", name='" + name + "\'}";

View file

@ -1,5 +1,18 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models; package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import java.util.Optional;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
public interface RoomRepository extends CrudRepository<Room, Long> {} public interface RoomRepository extends CrudRepository<Room, Long> {
/**
* Finds a room by their id and a username
*
* @param id the room id
* @param username a User's username
* @return an optional device, empty if none found
*/
@Query("SELECT r FROM Room r JOIN r.user u WHERE r.id = ?1 AND u.username = ?2")
Optional<Room> findByIdAndUsername(Long id, String username);
}

View file

@ -35,7 +35,7 @@ public class Sensor extends InputDevice {
} }
/** The value of this sensor according to its sensor type */ /** The value of this sensor according to its sensor type */
@Column(nullable = false, length = 10, precision = 1) @Column(nullable = false, precision = 11, scale = 1)
private BigDecimal value; private BigDecimal value;
/** The type of this sensor */ /** The type of this sensor */

View file

@ -1,5 +1,6 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models; package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import java.math.BigDecimal;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@ -8,11 +9,28 @@ import javax.validation.constraints.NotNull;
@Entity @Entity
public class SmartPlug extends Switchable { public class SmartPlug extends Switchable {
/** The average consumption of an active plug when on in Watt */
public static final Double AVERAGE_CONSUMPTION_KW = 200.0;
/** The total amount of power that the smart plug has consumed represented in W/h */
@Column(precision = 13, scale = 3)
@NotNull
private BigDecimal totalConsumption = BigDecimal.ZERO;
/** Whether the smart plug is on */ /** Whether the smart plug is on */
@Column(name = "smart_plug_on", nullable = false) @Column(name = "smart_plug_on", nullable = false)
@NotNull @NotNull
private boolean on; private boolean on;
public BigDecimal getTotalConsumption() {
return totalConsumption;
}
/** Resets the consuption meter */
public void resetTotalConsumption() {
totalConsumption = BigDecimal.ZERO;
}
@Override @Override
public boolean isOn() { public boolean isOn() {
return on; return on;
@ -24,6 +42,6 @@ public class SmartPlug extends Switchable {
} }
public SmartPlug() { public SmartPlug() {
super("smart-plug"); super("smartPlug");
} }
} }

View file

@ -1,3 +1,24 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models; package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
public interface SmartPlugRepository extends SwitchableRepository<SmartPlug> {} import java.util.Collection;
import javax.transaction.Transactional;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
public interface SmartPlugRepository extends SwitchableRepository<SmartPlug> {
@Transactional
Collection<SmartPlug> findByOn(boolean on);
/**
* Updates total consumption of all activated smart plugs by considering a load of
* fakeConsumption W. This query must be executed every second
*
* @see ch.usi.inf.sa4.sanmarinoes.smarthut.scheduled.UpdateTasks
* @param fakeConsumption the fake consumption in watts
*/
@Modifying(clearAutomatically = true)
@Transactional
@Query(
"UPDATE SmartPlug s SET totalConsumption = s.totalConsumption + ?1 / 3600.0 WHERE s.on = true")
void updateTotalConsumption(Double fakeConsumption);
}

View file

@ -5,6 +5,7 @@ import java.util.Set;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
import javax.persistence.PreRemove;
/** A switch input device */ /** A switch input device */
@Entity @Entity
@ -51,4 +52,11 @@ public class Switch extends InputDevice {
public Set<Switchable> getOutputs() { public Set<Switchable> getOutputs() {
return switchables; return switchables;
} }
@PreRemove
public void removeSwitchable() {
for (Switchable s : getOutputs()) {
s.setSwitchId(null);
}
}
} }

View file

@ -1,3 +1,9 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models; package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
public interface SwitchRepository extends DeviceRepository<Switch> {} import javax.transaction.Transactional;
public interface SwitchRepository extends DeviceRepository<Switch> {
@Transactional
void deleteAllByRoomId(long roomId);
}

View file

@ -1,5 +1,6 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models; package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import java.util.Objects; import java.util.Objects;
import javax.persistence.*; import javax.persistence.*;
@ -24,6 +25,7 @@ public class User {
/** A properly salted way to store the password */ /** A properly salted way to store the password */
@Column(nullable = false) @Column(nullable = false)
@GsonExclude
private String password; private String password;
/** /**
@ -34,7 +36,7 @@ public class User {
private String email; private String email;
@Column(nullable = false) @Column(nullable = false)
@ApiModelProperty(hidden = true) @GsonExclude
private Boolean isEnabled = false; private Boolean isEnabled = false;
public Long getId() { public Long getId() {

View file

@ -2,10 +2,10 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.scheduled;
import ch.usi.inf.sa4.sanmarinoes.smarthut.controller.MotionSensorController; import ch.usi.inf.sa4.sanmarinoes.smarthut.controller.MotionSensorController;
import ch.usi.inf.sa4.sanmarinoes.smarthut.controller.SensorController; import ch.usi.inf.sa4.sanmarinoes.smarthut.controller.SensorController;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.MotionSensorRepository; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Sensor; import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.SensorRepository;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Collection;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
@ -13,18 +13,25 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** Generates fake sensor (and motion sensor) updates as required by milestone one */ /**
* Generates fake sensor (and motion sensor) and smart plug consumption updates as required by
* milestone one
*/
@Component @Component
public class SensorUpdateTasks { public class UpdateTasks {
@Autowired private SensorRepository sensorRepository; @Autowired private SensorRepository sensorRepository;
@Autowired private MotionSensorRepository motionSensorRepository; @Autowired private MotionSensorRepository motionSensorRepository;
@Autowired private SmartPlugRepository smartPlugRepository;
@Autowired private SensorController sensorController; @Autowired private SensorController sensorController;
@Autowired private MotionSensorController motionSensorController; @Autowired private MotionSensorController motionSensorController;
@Autowired private SensorSocketEndpoint sensorSocketEndpoint;
/** Generates fake sensor updates every two seconds with a +/- 1.25% error */ /** Generates fake sensor updates every two seconds with a +/- 1.25% error */
@Scheduled(fixedRate = 2000) @Scheduled(fixedRate = 2000)
public void sensorFakeUpdate() { public void sensorFakeUpdate() {
@ -59,4 +66,12 @@ public class SensorUpdateTasks {
sensor, false)); sensor, false));
}); });
} }
/** Updates power consumption of all activated smart plugs every second */
@Scheduled(fixedDelay = 1000)
public void smartPlugConsumptionFakeUpdate() {
smartPlugRepository.updateTotalConsumption(SmartPlug.AVERAGE_CONSUMPTION_KW);
final Collection<SmartPlug> c = smartPlugRepository.findByOn(true);
c.forEach(s -> sensorSocketEndpoint.broadcast(s, sensorRepository.findUser(s.getId())));
}
} }

View file

@ -1,13 +1,12 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.socket; package ch.usi.inf.sa4.sanmarinoes.smarthut.socket;
import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.didThrow;
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonConfig; import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonConfig;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User;
import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps; import com.google.common.collect.Multimaps;
import com.google.gson.Gson; import com.google.gson.Gson;
import java.io.IOException;
import java.util.*; import java.util.*;
import javax.websocket.*; import javax.websocket.*;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -57,12 +56,19 @@ public class SensorSocketEndpoint extends Endpoint {
* @param u the user to which to send the message * @param u the user to which to send the message
* @return number of successful transfer * @return number of successful transfer
*/ */
public long broadcast(Object message, User u) { public void broadcast(Object message, User u) {
final Collection<Session> sessions = authorizedClients.get(u); final HashSet<Session> sessions = new HashSet<>(authorizedClients.get(u));
return sessions.stream() for (Session s : sessions) {
.parallel() try {
.filter(didThrow(s -> s.getBasicRemote().sendText(gson.toJson(message)))) if (s.isOpen()) {
.count(); s.getBasicRemote().sendText(gson.toJson(message));
} else {
authorizedClients.remove(u, s);
}
} catch (IOException e) {
e.printStackTrace();
}
}
} }
/** /**

View file

@ -0,0 +1,35 @@
spring.http.converters.preferred-json-mapper=gson
spring.datasource.url=jdbc:postgresql://localhost:5432/smarthut
spring.datasource.username=postgres
spring.datasource.password=
# Hibernate properties
spring.jpa.database=POSTGRESQL
spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl
spring.jpa.properties.hibernate.format_sql=true
jwt.secret=thiskeymustbeverylongorthethingcomplainssoiamjustgoingtowritehereabunchofgarbageciaomamma
spring.mail.test-connection=true
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.username=smarthut.sm@gmail.com
spring.mail.password=dcadvbagqfkwbfts
spring.mail.properties.mail.smtp.starttls.required=true
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.connectiontimeout=5000
spring.mail.properties.mail.smtp.timeout=5000
spring.mail.properties.mail.smtp.writetimeout=5000
email.registrationSubject=Complete your SmartHut.sm registration
email.registration=To confirm your registration, please click here:
email.registrationPath=http://localhost:8080/register/confirm-account?token=
email.registrationRedirect=http://localhost:3000
email.resetpasswordSubject=SmartHut.sm password reset
email.resetpassword=To reset your password, please click here:
email.resetpasswordPath=http://localhost:3000/password-reset?token=
email.resetPasswordRedirect=http://localhost:3000/conf-reset-pass

View file

@ -0,0 +1,42 @@
spring.http.converters.preferred-json-mapper=gson
# Database connection properties
spring.datasource.url=${POSTGRES_JDBC}
spring.datasource.username=${POSTGRES_USER}
spring.datasource.password=${POSTGRES_PASS}
# Hibernate properties
spring.jpa.database=POSTGRESQL
spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl
spring.jpa.properties.hibernate.format_sql=true
# JWT secret
jwt.secret=${SECRET}
# Mail connection properties
spring.mail.test-connection=true
spring.mail.host=${MAIL_HOST}
spring.mail.port=${MAIL_PORT}
spring.mail.properties.mail.smtp.starttls.enable=${MAIL_STARTTLS}
spring.mail.username=${MAIL_USER}
spring.mail.password=${MAIL_PASS}
spring.mail.properties.mail.smtp.starttls.required=${MAIL_STARTTLS}
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.connectiontimeout=5000
spring.mail.properties.mail.smtp.timeout=5000
spring.mail.properties.mail.smtp.writetimeout=5000
# Registration email properties
email.registrationSubject=Complete your SmartHut.sm registration
email.registration=To confirm your registration, please click here:
email.registrationPath=${BACKEND_URL}/register/confirm-account?token=
email.registrationSuccess=${FRONTEND_URL}
# Password reset email properties
email.resetpasswordSubject=SmartHut.sm password reset
email.resetpassword=To reset your password, please click here:
email.resetpasswordPath=${FRONTEND_URL}/password-reset?token=
email.resetPasswordSuccess=${FRONTEND_URL}/conf-reset-pass

View file

@ -1,33 +1 @@
spring.http.converters.preferred-json-mapper=gson spring.profiles.active=${SMARTHUT_THIS_VALUE_IS_PROD_IF_THIS_IS_A_CONTAINER_PIZZOCCHERI:dev}
spring.datasource.url=jdbc:postgresql://localhost:5432/smarthut
spring.datasource.username=postgres
spring.datasource.password=
# Hibernate properties
spring.jpa.database=POSTGRESQL
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl
spring.jpa.properties.hibernate.format_sql=true
jwt.secret=thiskeymustbeverylongorthethingcomplainssoiamjustgoingtowritehereabunchofgarbageciaomamma
spring.mail.test-connection=true
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.username=smarthut.sm@gmail.com
spring.mail.password=dcadvbagqfkwbfts
spring.mail.properties.mail.smtp.starttls.required=true
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.connectiontimeout=5000
spring.mail.properties.mail.smtp.timeout=5000
spring.mail.properties.mail.smtp.writetimeout=5000
email.registrationSubject=Complete your SmartHut.sm registration
email.registration=To confirm your registration, please click here:
email.registrationPath=http://localhost:8080/register/confirm-account?token=
email.resetpasswordSubject=SmartHut.sm password reset
email.resetpassword=To reset your password, please click here:
email.resetpasswordPath=http://localhost:3000/password-reset?token=

View file

@ -8,6 +8,8 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.OkResponse;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserRegistrationRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserRegistrationRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateRegistrationException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateRegistrationException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UnauthorizedException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UnauthorizedException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationTokenRepository;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import java.util.Map; import java.util.Map;
@ -25,6 +27,10 @@ public class AuthenticationTests extends SmartHutTest {
@Autowired private TestRestTemplate restTemplate; @Autowired private TestRestTemplate restTemplate;
@Autowired private UserRepository userRepository;
@Autowired private ConfirmationTokenRepository tokenRepository;
private UserRegistrationRequest getDisabledUser() { private UserRegistrationRequest getDisabledUser() {
final UserRegistrationRequest disabledUser = new UserRegistrationRequest(); final UserRegistrationRequest disabledUser = new UserRegistrationRequest();
disabledUser.setName("Disabled User"); disabledUser.setName("Disabled User");
@ -34,15 +40,6 @@ public class AuthenticationTests extends SmartHutTest {
return disabledUser; 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 @Override
protected void setUp() { protected void setUp() {
final ResponseEntity<OkResponse> res = final ResponseEntity<OkResponse> res =
@ -50,12 +47,7 @@ public class AuthenticationTests extends SmartHutTest {
this.url("/register"), getDisabledUser(), OkResponse.class); this.url("/register"), getDisabledUser(), OkResponse.class);
assertThat(res.getStatusCode().equals(HttpStatus.OK)); assertThat(res.getStatusCode().equals(HttpStatus.OK));
final ResponseEntity<OkResponse> res2 = registerTestUser(restTemplate, userRepository, tokenRepository);
this.restTemplate.postForEntity(
this.url("/register"), enabledUser, OkResponse.class);
assertThat(res2.getStatusCode().equals(HttpStatus.OK));
// TODO: email confirmation for enabledUser
} }
@Test @Test
@ -230,4 +222,18 @@ public class AuthenticationTests extends SmartHutTest {
assertThat(res.getBody() != null); assertThat(res.getBody() != null);
assertThat(res.getBody().isUserDisabled()); assertThat(res.getBody().isUserDisabled());
} }
@Test
public void loginShouldReturnTokenWithEnabledUser() {
final JWTRequest request = new JWTRequest();
request.setUsernameOrEmail("enabled");
request.setPassword("password");
final ResponseEntity<JWTResponse> res =
this.restTemplate.postForEntity(url("/auth/login"), request, JWTResponse.class);
assertThat(res.getStatusCode().equals(HttpStatus.OK));
assertThat(res.getBody() != null);
assertThat(res.getBody().getToken() != null);
assertThat(!res.getBody().getToken().isEmpty());
}
} }

View file

@ -0,0 +1,46 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut;
import static org.junit.jupiter.api.Assertions.*;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DimmableLight;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.KnobDimmer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@DisplayName("A Knob Dimmer")
public class KnobDimmerTests {
KnobDimmer knobDimmer;
@BeforeEach
public void createNewKnobDimmer() {
this.knobDimmer = new KnobDimmer();
}
@Nested
@DisplayName(" when multiple lights are present")
class MultipleLights {
@BeforeEach
public void setLights() {
DimmableLight dl;
for (int i = 0; i < 3; i++) {
dl = new DimmableLight();
dl.setIntensity(10);
;
knobDimmer.addDimmableLight(dl);
}
}
@Test
@DisplayName(" set the intensity ")
public void increase() {
knobDimmer.setLightIntensity(30);
for (DimmableLight dl : knobDimmer.getOutputs()) {
assertEquals(30, dl.getIntensity());
}
}
}
}

View file

@ -1,6 +1,18 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut; package ch.usi.inf.sa4.sanmarinoes.smarthut;
import static org.assertj.core.api.Assertions.assertThat;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.OkResponse;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserRegistrationRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationToken;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationTokenRepository;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.reactive.function.client.WebClient;
public abstract class SmartHutTest { public abstract class SmartHutTest {
private boolean setupDone = false; private boolean setupDone = false;
@ -15,6 +27,38 @@ public abstract class SmartHutTest {
protected void setUp() {} protected void setUp() {}
protected static final UserRegistrationRequest enabledUser = new UserRegistrationRequest();
static {
enabledUser.setName("Enabled User");
enabledUser.setEmail("enabled@example.com");
enabledUser.setUsername("enabled");
enabledUser.setPassword("password");
}
protected void registerTestUser(
final TestRestTemplate restTemplate,
final UserRepository userRepository,
final ConfirmationTokenRepository tokenRepository) {
final ResponseEntity<OkResponse> res2 =
restTemplate.postForEntity(this.url("/register"), enabledUser, OkResponse.class);
assertThat(res2.getStatusCode().equals(HttpStatus.OK));
final User persistedEnabledUser = userRepository.findByUsername("enabled");
final ConfirmationToken token = tokenRepository.findByUser(persistedEnabledUser);
final ResponseEntity<Void> res3 =
WebClient.create(getBaseURL())
.get()
.uri("/register/confirm-account?token=" + token.getConfirmationToken())
.retrieve()
.toBodilessEntity()
.block();
assertThat(res3.getStatusCode().is2xxSuccessful());
assertThat(userRepository.findByUsername("enabled").getEnabled());
}
@BeforeEach @BeforeEach
void setUpHack() { void setUpHack() {
if (!setupDone) { if (!setupDone) {

View file

@ -29,7 +29,9 @@ server.port = 2000
email.registrationSubject=Complete your SmartHut.sm registration email.registrationSubject=Complete your SmartHut.sm registration
email.registration=To confirm your registration, please click here: email.registration=To confirm your registration, please click here:
email.registrationPath=http://localhost:8080/register/confirm-account?token= email.registrationPath=http://localhost:8080/register/confirm-account?token=
email.registrationRedirect=http://localhost:3000
email.resetpasswordSubject=SmartHut.sm password reset email.resetpasswordSubject=SmartHut.sm password reset
email.resetpassword=To reset your password, please click here: email.resetpassword=To reset your password, please click here:
email.resetpasswordPath=http://localhost:3000/password-reset?token= email.resetpasswordPath=http://localhost:3000/password-reset?token=
email.resetPasswordRedirect=http://localhost:3000/conf-reset-pass