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

4
.gitignore vendored
View File

@ -1,5 +1,7 @@
.idea/**
data/**/*
**/.DS_Store
# Compiled class file
@ -138,4 +140,4 @@ gradle-app.setting
# gradle/wrapper/gradle-wrapper.properties
# IntelliJ
*.iml
*.iml

View File

@ -62,4 +62,4 @@ code_quality:
paths:
- build/reports/cpd/cpdCheck.xml
#create a report on the quality of the code
expose_as: 'Code Quality Report'
expose_as: 'Code Quality Report'

View File

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

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

View File

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

View File

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

View File

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

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

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

View File

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

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

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

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

View File

@ -23,6 +23,12 @@ public class RoomController {
@Autowired private DeviceRepository<Device> deviceRepository;
@Autowired private SwitchRepository switchRepository;
@Autowired private ButtonDimmerRepository buttonDimmerRepository;
@Autowired private KnobDimmerRepository knobDimmerRepository;
@GetMapping
public List<Room> findAll() {
return toList(roomRepository.findAll());
@ -33,44 +39,57 @@ public class RoomController {
return roomRepository.findById(id).orElseThrow(NotFoundException::new);
}
private Room save(final RoomSaveRequest r, final Principal principal, boolean setWhenNull) {
Room newRoom = new Room();
@PostMapping
public @ResponseBody Room create(
@Valid @RequestBody RoomSaveRequest r, final Principal principal) {
final String username = principal.getName();
final Long userId = userRepository.findByUsername(username).getId();
final String img = r.getImage();
final Room.Icon icon = r.getIcon();
final Room newRoom = new Room();
newRoom.setUserId(userId);
newRoom.setName(r.getName());
if (img != null) {
newRoom.setImage(img);
} else if (setWhenNull) {
newRoom.setImage(null);
newRoom.setImage(img);
newRoom.setIcon(icon);
return roomRepository.save(newRoom);
}
@PutMapping("/{id}")
public @ResponseBody Room update(
@PathVariable("id") long id, @RequestBody RoomSaveRequest r, final Principal principal)
throws NotFoundException {
final Room newRoom =
roomRepository
.findByIdAndUsername(id, principal.getName())
.orElseThrow(NotFoundException::new);
final String img = r.getImage();
final Room.Icon icon = r.getIcon();
if (r.getName() != null) {
newRoom.setName(r.getName());
}
if ("".equals(img)) {
newRoom.setImage(null);
} else if (img != null) {
newRoom.setImage(img);
}
if (icon != null) {
newRoom.setIcon(icon);
} else if (setWhenNull) {
newRoom.setIcon(null);
}
return roomRepository.save(newRoom);
}
@PostMapping
public @ResponseBody Room create(
@Valid @RequestBody RoomSaveRequest r, final Principal principal) {
return this.save(r, principal, true);
}
@PutMapping
public @ResponseBody Room update(
@Valid @RequestBody RoomSaveRequest r, final Principal principal) {
return this.save(r, principal, false);
}
@DeleteMapping("/{id}")
public void deleteById(@PathVariable("id") long id) {
switchRepository.deleteAllByRoomId(id);
knobDimmerRepository.deleteAllByRoomId(id);
buttonDimmerRepository.deleteAllByRoomId(id);
roomRepository.deleteById(id);
}
@ -79,7 +98,7 @@ public class RoomController {
* id).
*/
@GetMapping(path = "/{roomId}/devices")
public @ResponseBody List<? extends Device> getDevices(@PathVariable("roomId") long roomid) {
public List<Device> getDevices(@PathVariable("roomId") long 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.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
import java.security.Principal;
import java.util.*;
import java.util.List;
import javax.validation.Valid;
@ -14,7 +15,7 @@ import org.springframework.web.bind.annotation.*;
@RestController
@EnableAutoConfiguration
@RequestMapping("/smartplug")
@RequestMapping("/smartPlug")
public class SmartPlugController {
@Autowired private SmartPlugRepository smartPlugRepository;
@ -44,9 +45,25 @@ public class SmartPlugController {
}
@PutMapping
public SmartPlug update(@Valid @RequestBody SmartPlugSaveRequest sp) throws NotFoundException {
public SmartPlug update(@Valid @RequestBody SmartPlugSaveRequest sp, final Principal principal)
throws NotFoundException {
return save(
smartPlugRepository.findById(sp.getId()).orElseThrow(NotFoundException::new), sp);
smartPlugRepository
.findByIdAndUsername(sp.getId(), principal.getName())
.orElseThrow(NotFoundException::new),
sp);
}
@DeleteMapping("/{id}/meter")
public SmartPlug resetMeter(@PathVariable("id") long id, final Principal principal)
throws NotFoundException {
final SmartPlug s =
smartPlugRepository
.findByIdAndUsername(id, principal.getName())
.orElseThrow(NotFoundException::new);
s.resetTotalConsumption();
return smartPlugRepository.save(s);
}
@DeleteMapping("/{id}")

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.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
import java.security.Principal;
import java.util.*;
import java.util.List;
import javax.validation.Valid;
@ -55,9 +56,13 @@ public class SwitchController extends InputDeviceConnectionController<Switch, Sw
}
@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 {
final Switch s = switchRepository.findById(sr.getId()).orElseThrow(NotFoundException::new);
final Switch s =
switchRepository
.findByIdAndUsername(sr.getId(), principal.getName())
.orElseThrow(NotFoundException::new);
switch (sr.getType()) {
case ON:

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,9 @@
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> {
ConfirmationToken findByConfirmationToken(String confirmationToken);
ConfirmationToken findByUser(User user);
@Transactional
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
* a REST call.
*/
@Column(name = "room_id", nullable = false, unique = true)
@Column(name = "room_id", nullable = false)
@NotNull
private Long roomId;
@ -49,17 +49,13 @@ public abstract class Device {
* The name for the category of this particular device (e.g 'dimmer'). Not stored in the
* database but set thanks to constructors
*/
@ApiModelProperty(hidden = true)
@Transient
private final String kind;
@Transient private final String kind;
/**
* The way this device behaves in the automation flow. Not stored in the database but set thanks
* to constructors
*/
@ApiModelProperty(hidden = true)
@Transient
private final FlowType flowType;
@Transient private final FlowType flowType;
public long getId() {
return id;

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,9 @@
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() {
super("motion-sensor");
super("motionSensor");
}
}

View File

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

View File

@ -1,5 +1,18 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import java.util.Optional;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
public interface RoomRepository extends CrudRepository<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 */
@Column(nullable = false, length = 10, precision = 1)
@Column(nullable = false, precision = 11, scale = 1)
private BigDecimal value;
/** The type of this sensor */

View File

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

View File

@ -1,3 +1,24 @@
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.Entity;
import javax.persistence.OneToMany;
import javax.persistence.PreRemove;
/** A switch input device */
@Entity
@ -51,4 +52,11 @@ public class Switch extends InputDevice {
public Set<Switchable> getOutputs() {
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;
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;
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
import io.swagger.annotations.ApiModelProperty;
import java.util.Objects;
import javax.persistence.*;
@ -24,6 +25,7 @@ public class User {
/** A properly salted way to store the password */
@Column(nullable = false)
@GsonExclude
private String password;
/**
@ -34,7 +36,7 @@ public class User {
private String email;
@Column(nullable = false)
@ApiModelProperty(hidden = true)
@GsonExclude
private Boolean isEnabled = false;
public Long getId() {

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.SensorController;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.MotionSensorRepository;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Sensor;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.SensorRepository;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.StreamSupport;
@ -13,18 +13,25 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/** Generates fake sensor (and motion sensor) updates as required by milestone one */
/**
* Generates fake sensor (and motion sensor) and smart plug consumption updates as required by
* milestone one
*/
@Component
public class SensorUpdateTasks {
public class UpdateTasks {
@Autowired private SensorRepository sensorRepository;
@Autowired private MotionSensorRepository motionSensorRepository;
@Autowired private SmartPlugRepository smartPlugRepository;
@Autowired private SensorController sensorController;
@Autowired private MotionSensorController motionSensorController;
@Autowired private SensorSocketEndpoint sensorSocketEndpoint;
/** Generates fake sensor updates every two seconds with a +/- 1.25% error */
@Scheduled(fixedRate = 2000)
public void sensorFakeUpdate() {
@ -59,4 +66,12 @@ public class SensorUpdateTasks {
sensor, false));
});
}
/** Updates power consumption of all activated smart plugs every second */
@Scheduled(fixedDelay = 1000)
public void smartPlugConsumptionFakeUpdate() {
smartPlugRepository.updateTotalConsumption(SmartPlug.AVERAGE_CONSUMPTION_KW);
final Collection<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;
import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.didThrow;
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonConfig;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.gson.Gson;
import java.io.IOException;
import java.util.*;
import javax.websocket.*;
import org.springframework.beans.factory.annotation.Autowired;
@ -57,12 +56,19 @@ public class SensorSocketEndpoint extends Endpoint {
* @param u the user to which to send the message
* @return number of successful transfer
*/
public long broadcast(Object message, User u) {
final Collection<Session> sessions = authorizedClients.get(u);
return sessions.stream()
.parallel()
.filter(didThrow(s -> s.getBasicRemote().sendText(gson.toJson(message))))
.count();
public void broadcast(Object message, User u) {
final HashSet<Session> sessions = new HashSet<>(authorizedClients.get(u));
for (Session s : sessions) {
try {
if (s.isOpen()) {
s.getBasicRemote().sendText(gson.toJson(message));
} else {
authorizedClients.remove(u, s);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**

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.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=
spring.profiles.active=${SMARTHUT_THIS_VALUE_IS_PROD_IF_THIS_IS_A_CONTAINER_PIZZOCCHERI:dev}

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.error.DuplicateRegistrationException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UnauthorizedException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationTokenRepository;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import java.util.Map;
@ -25,6 +27,10 @@ public class AuthenticationTests extends SmartHutTest {
@Autowired private TestRestTemplate restTemplate;
@Autowired private UserRepository userRepository;
@Autowired private ConfirmationTokenRepository tokenRepository;
private UserRegistrationRequest getDisabledUser() {
final UserRegistrationRequest disabledUser = new UserRegistrationRequest();
disabledUser.setName("Disabled User");
@ -34,15 +40,6 @@ public class AuthenticationTests extends SmartHutTest {
return disabledUser;
}
private static final UserRegistrationRequest enabledUser = new UserRegistrationRequest();
static {
enabledUser.setName("Enabled User");
enabledUser.setEmail("enabled@example.com");
enabledUser.setUsername("enabled");
enabledUser.setPassword("password");
}
@Override
protected void setUp() {
final ResponseEntity<OkResponse> res =
@ -50,12 +47,7 @@ public class AuthenticationTests extends SmartHutTest {
this.url("/register"), getDisabledUser(), OkResponse.class);
assertThat(res.getStatusCode().equals(HttpStatus.OK));
final ResponseEntity<OkResponse> res2 =
this.restTemplate.postForEntity(
this.url("/register"), enabledUser, OkResponse.class);
assertThat(res2.getStatusCode().equals(HttpStatus.OK));
// TODO: email confirmation for enabledUser
registerTestUser(restTemplate, userRepository, tokenRepository);
}
@Test
@ -230,4 +222,18 @@ public class AuthenticationTests extends SmartHutTest {
assertThat(res.getBody() != null);
assertThat(res.getBody().isUserDisabled());
}
@Test
public void loginShouldReturnTokenWithEnabledUser() {
final JWTRequest request = new JWTRequest();
request.setUsernameOrEmail("enabled");
request.setPassword("password");
final ResponseEntity<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;
import static org.assertj.core.api.Assertions.assertThat;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.OkResponse;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserRegistrationRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationToken;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationTokenRepository;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.reactive.function.client.WebClient;
public abstract class SmartHutTest {
private boolean setupDone = false;
@ -15,6 +27,38 @@ public abstract class SmartHutTest {
protected void setUp() {}
protected static final UserRegistrationRequest enabledUser = new UserRegistrationRequest();
static {
enabledUser.setName("Enabled User");
enabledUser.setEmail("enabled@example.com");
enabledUser.setUsername("enabled");
enabledUser.setPassword("password");
}
protected void registerTestUser(
final TestRestTemplate restTemplate,
final UserRepository userRepository,
final ConfirmationTokenRepository tokenRepository) {
final ResponseEntity<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
void setUpHack() {
if (!setupDone) {

View File

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