diff --git a/.gitignore b/.gitignore
index 69ca520..1b1a367 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+.idea/**
+
**/.DS_Store
# Compiled class file
diff --git a/.idea/.name b/.idea/.name
deleted file mode 100644
index 03128f2..0000000
--- a/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-smarthut
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
deleted file mode 100644
index fdc392f..0000000
--- a/.idea/jarRepositories.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 7c6191b..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1dd..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index ea7614f..728859b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,6 +10,7 @@ sourceCompatibility = "11"
repositories {
mavenCentral()
+ jcenter()
}
dependencies {
@@ -20,6 +21,8 @@ dependencies {
implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'org.springframework.security:spring-security-web'
implementation 'org.postgresql:postgresql'
+ compile "io.springfox:springfox-swagger2:2.9.2"
+ compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2'
implementation('org.springframework.boot:spring-boot-starter-web') {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-json'
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonConfig.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonConfig.java
new file mode 100644
index 0000000..3a2ab37
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonConfig.java
@@ -0,0 +1,36 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.config;
+
+import com.google.gson.*;
+import java.lang.reflect.Type;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.converter.json.GsonHttpMessageConverter;
+import springfox.documentation.spring.web.json.Json;
+
+/**
+ * Spring configuration in order to register the GSON type adapter needed to avoid serializing twice
+ * Springfox Swagger JSON output (see: https://stackoverflow.com/a/30220562)
+ */
+@Configuration
+public class GsonConfig {
+ @Bean
+ public GsonHttpMessageConverter gsonHttpMessageConverter() {
+ GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
+ converter.setGson(gson());
+ return converter;
+ }
+
+ private Gson gson() {
+ final GsonBuilder builder = new GsonBuilder();
+ builder.registerTypeAdapter(Json.class, new SpringfoxJsonToGsonAdapter());
+ return builder.create();
+ }
+}
+
+/** GSON type adapter needed to avoid serializing twice Springfox Swagger JSON output */
+class SpringfoxJsonToGsonAdapter implements JsonSerializer {
+ @Override
+ public JsonElement serialize(Json json, Type type, JsonSerializationContext context) {
+ return JsonParser.parseString(json.value());
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/SpringFoxConfig.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/SpringFoxConfig.java
new file mode 100644
index 0000000..743d119
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/SpringFoxConfig.java
@@ -0,0 +1,77 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.config;
+
+import static springfox.documentation.builders.PathSelectors.regex;
+
+import java.util.List;
+import java.util.function.Predicate;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.ApiKey;
+import springfox.documentation.service.SecurityScheme;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+/**
+ * This class configures the automated REST documentation tool Swagger for this project. The
+ * documentation can be seen by going to http://localhost:8080/swaggeer-ui.html
+ */
+@Configuration
+@EnableSwagger2
+@ComponentScan("ch.usi.inf.sa4.sanmarinoes.smarthut")
+public class SpringFoxConfig {
+
+ /**
+ * Main definition of Springfox / swagger configuration
+ *
+ * @return a Docket object containing the swagger configuration
+ */
+ @Bean
+ public Docket api() {
+ return new Docket(DocumentationType.SWAGGER_2)
+ .select()
+ .apis(RequestHandlerSelectors.any())
+ .paths(paths()::test)
+ .build()
+ .apiInfo(apiInfo())
+ .securitySchemes(securitySchemes());
+ }
+
+ /**
+ * Configures the documentation about the smarthut authentication system
+ *
+ * @return a list of springfox authentication configurations
+ */
+ private static List extends SecurityScheme> securitySchemes() {
+ return List.of(new ApiKey("Bearer", "Authorization", "header"));
+ }
+
+ /**
+ * Configures the paths the documentation must be generated for. Add a path here only when the
+ * spec has been totally defined.
+ *
+ * @return A predicate that tests whether a path must be included or not
+ */
+ private Predicate paths() {
+ return regex("/auth.*")::apply;
+ }
+
+ /**
+ * Returns the metadata about the smarthut project
+ *
+ * @return metadata about smarthut
+ */
+ private ApiInfo apiInfo() {
+ return new ApiInfoBuilder()
+ .title("SmartHut.sm API")
+ .description("Backend API for the SanMariones version of the SA4 SmartHut project")
+ .termsOfServiceUrl("https://www.youtube.com/watch?v=9KxTcDsy9Gs")
+ .license("WTFPL")
+ .version("dev branch")
+ .build();
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/WebSecurityConfig.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/WebSecurityConfig.java
index a8bcb1d..bb548f0 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/WebSecurityConfig.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/WebSecurityConfig.java
@@ -50,13 +50,17 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
.disable()
// dont authenticate this particular request
.authorizeRequests()
- .antMatchers("/auth/login")
+ .antMatchers(
+ "/auth/login",
+ "/auth/register",
+ "/swagger-ui.html",
+ "/v2/api-docs",
+ "/webjars/**",
+ "/swagger-resources/**",
+ "/csrf")
.permitAll()
- .antMatchers("/auth/register")
- .permitAll()
- .
// all other requests need to be authenticated
- anyRequest()
+ .anyRequest()
.authenticated()
.and()
.
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/AuthenticationController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/AuthenticationController.java
index ed82692..e4c575e 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/AuthenticationController.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/AuthenticationController.java
@@ -5,9 +5,9 @@ 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.models.*;
+import io.swagger.annotations.Authorization;
import java.security.Principal;
import javax.validation.Valid;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
@@ -21,18 +21,31 @@ import org.springframework.web.bind.annotation.*;
@RequestMapping("/auth")
public class AuthenticationController {
- @Autowired private AuthenticationManager authenticationManager;
+ private final AuthenticationManager authenticationManager;
- @Autowired private UserRepository userRepository;
+ private final UserRepository userRepository;
- @Autowired private JWTTokenUtil jwtTokenUtil;
+ private final JWTTokenUtil jwtTokenUtil;
- @Autowired private JWTUserDetailsService userDetailsService;
+ private final JWTUserDetailsService userDetailsService;
- @Autowired private UserRepository users;
+ private final UserRepository users;
private BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
+ public AuthenticationController(
+ AuthenticationManager authenticationManager,
+ UserRepository userRepository,
+ JWTTokenUtil jwtTokenUtil,
+ JWTUserDetailsService userDetailsService,
+ UserRepository users) {
+ this.authenticationManager = authenticationManager;
+ this.userRepository = userRepository;
+ this.jwtTokenUtil = jwtTokenUtil;
+ this.userDetailsService = userDetailsService;
+ this.users = users;
+ }
+
@PostMapping("/login")
public JWTResponse login(@RequestBody JWTRequest authenticationRequest) throws Exception {
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
@@ -49,15 +62,18 @@ public class AuthenticationController {
return user;
}
+ @Authorization(value = "Bearer")
@PatchMapping("/update")
- public User update(@Valid @RequestBody final UserUpdateRequest u, final Principal principal) {
+ public User update(
+ @Valid @RequestBody final UserUpdateRequest userData, final Principal principal) {
final User oldUser = userRepository.findByUsername(principal.getName());
- if (u.getName() != null) oldUser.setName(u.getName());
- if (u.getEmail() != null) {
- oldUser.setEmail(u.getEmail());
+ if (userData.getName() != null) oldUser.setName(userData.getName());
+ if (userData.getEmail() != null) {
+ oldUser.setEmail(userData.getEmail());
// TODO: handle email verification
}
- if (u.getPassword() != null) oldUser.setPassword(encoder.encode(u.getPassword()));
+ if (userData.getPassword() != null)
+ oldUser.setPassword(encoder.encode(userData.getPassword()));
return userRepository.save(oldUser);
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/RoomController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/RoomController.java
index b73c55a..c1a4d87 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/RoomController.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/RoomController.java
@@ -1,11 +1,12 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.RoomSaveRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
+import java.security.Principal;
import java.util.*;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.*;
-import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;
@@ -30,27 +31,38 @@ public class RoomController {
return roomRepository.findById(id);
}
- @PostMapping
- public Room save(@Valid @RequestBody Room r) {
- final Object principal =
- SecurityContextHolder.getContext().getAuthentication().getPrincipal();
-
- if (!(principal instanceof UserDetails)) {
- throw new IllegalStateException("User is not logged in");
- }
+ private Room save(final RoomSaveRequest r, final Principal principal, boolean setWhenNull) {
+ Room newRoom = new Room();
final String username = ((UserDetails) principal).getUsername();
final Long userId = userRepository.findByUsername(username).getId();
+ final String img = r.getImage();
+ final String icon = r.getIcon();
- r.setUserId(userId);
- r.setUser(null);
+ newRoom.setUserId(userId);
+ newRoom.setUser(null);
+ if (img != null) {
+ newRoom.setImage(img.getBytes());
+ } else if (setWhenNull) {
+ newRoom.setImage(null);
+ }
+ if (icon != null) {
+ newRoom.setIcon(icon.getBytes());
+ } else if (setWhenNull) {
+ newRoom.setIcon(null);
+ }
- return roomRepository.save(r);
+ return roomRepository.save(newRoom);
+ }
+
+ @PostMapping
+ public Room create(@Valid @RequestBody RoomSaveRequest r, final Principal principal) {
+ return this.save(r, principal, true);
}
@PutMapping
- public Room update(@Valid @RequestBody Room r) {
- return roomRepository.save(r);
+ public Room update(@Valid @RequestBody RoomSaveRequest r, final Principal principal) {
+ return this.save(r, principal, false);
}
@DeleteMapping("/{id}")
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/RoomSaveRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/RoomSaveRequest.java
new file mode 100644
index 0000000..f813b1c
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/RoomSaveRequest.java
@@ -0,0 +1,43 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
+
+import javax.persistence.Lob;
+import javax.validation.constraints.NotNull;
+
+public class RoomSaveRequest {
+ /**
+ * Icon and image are to be given as byte[]. In order to get an encoded string from it, the
+ * Base64.getEncoder().encodeToString(byte[] content) should be used. For further information:
+ * https://www.baeldung.com/java-base64-image-string
+ * https://docs.oracle.com/javase/8/docs/api/java/util/Base64.html
+ */
+ @Lob private String icon;
+
+ @Lob private String image;
+
+ /** The user given name of this room (e.g. 'Master bedroom') */
+ @NotNull private String name;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getIcon() {
+ return icon;
+ }
+
+ public void setIcon(String icon) {
+ this.icon = icon;
+ }
+
+ public String getImage() {
+ return image;
+ }
+
+ public void setImage(String image) {
+ this.image = image;
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java
index 834104d..871b67c 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java
@@ -1,6 +1,7 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import com.google.gson.annotations.SerializedName;
+import io.swagger.annotations.ApiModelProperty;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
@@ -21,12 +22,14 @@ public abstract class Device {
/** Device identifier */
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
- @Column(name = "id", updatable = false, nullable = false)
+ @Column(name = "id", updatable = false, nullable = false, unique = true)
+ @ApiModelProperty(hidden = true)
private long id;
/** The room this device belongs in */
@ManyToOne
@JoinColumn(name = "room_id", nullable = false, updatable = false, insertable = false)
+ @ApiModelProperty(hidden = true)
private Room room;
/**
@@ -46,13 +49,17 @@ 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
*/
- @Transient private final String kind;
+ @ApiModelProperty(hidden = true)
+ @Transient
+ private final String kind;
/**
* The way this device behaves in the automation flow. Not stored in the database but set thanks
* to constructors
*/
- @Transient private final FlowType flowType;
+ @ApiModelProperty(hidden = true)
+ @Transient
+ private final FlowType flowType;
public long getId() {
return id;
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Room.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Room.java
index c0fae3f..4a82ab2 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Room.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Room.java
@@ -1,5 +1,6 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+import io.swagger.annotations.ApiModelProperty;
import java.util.Set;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
@@ -11,6 +12,7 @@ public class Room {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", updatable = false, nullable = false)
+ @ApiModelProperty(hidden = true)
private Long id;
/**
@@ -41,12 +43,14 @@ public class Room {
private String name;
/** Collection of devices present in this room */
+ @ApiModelProperty(hidden = true)
@OneToMany(mappedBy = "room")
private Set devices;
/** User that owns the house this room is in */
@ManyToOne
@JoinColumn(name = "user_id", nullable = false, updatable = false, insertable = false)
+ @ApiModelProperty(hidden = true)
private User user;
public Long getId() {
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Switch.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Switch.java
index fbf1eed..6f0eb99 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Switch.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Switch.java
@@ -1,11 +1,35 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+import javax.persistence.Column;
import javax.persistence.Entity;
/** A switch input device TODO: define switch behaviour (push button vs on/off state) */
@Entity
public class Switch extends InputDevice {
+
+ /** The state of this switch */
+ @Column(nullable = false, name = "switch_on")
+ private boolean on;
+
public Switch() {
super("switch");
}
+
+ /**
+ * Setter method for this Switch
+ *
+ * @param state The state to be set
+ */
+ void setState(boolean state) {
+ on = state;
+ }
+
+ /**
+ * Getter method for this Switch
+ *
+ * @return This Switch on state
+ */
+ boolean getState() {
+ return on;
+ }
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java
index 7909fe8..4b9cb51 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java
@@ -1,5 +1,6 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+import io.swagger.annotations.ApiModelProperty;
import java.util.Set;
import javax.persistence.*;
import javax.validation.constraints.*;
@@ -11,6 +12,7 @@ public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", updatable = false, nullable = false)
+ @ApiModelProperty(hidden = true)
private Long id;
/** The full name of the user */
@@ -40,7 +42,7 @@ public class User {
* The user's email (validated according to criteria used in >input type="email"<>
*
, technically not RFC 5322 compliant
*/
- @Column(nullable = false)
+ @Column(nullable = false, unique = true)
@NotNull
@NotEmpty(message = "Please provide an email")
@Email(message = "Please provide a valid email address")
@@ -49,6 +51,7 @@ public class User {
/** All rooms in the user's house */
@OneToMany(mappedBy = "user")
+ @ApiModelProperty(hidden = true)
private Set rooms;
public Long getId() {