Selaa lähdekoodia

First complete implementation of CRUD operations

Fabio Dalle Ave 1 kuukausi sitten
vanhempi
commit
7aa9ef34b7
40 muutettua tiedostoa jossa 1416 lisäystä ja 139 poistoa
  1. 5 0
      pom.xml
  2. 30 0
      src/main/java/it/bgates/remotebe/config/ApplicationConfig.java
  3. 1 0
      src/main/java/it/bgates/remotebe/config/BGatesUserDetailService.java
  4. 3 0
      src/main/java/it/bgates/remotebe/config/BGatesUserDetails.java
  5. 17 34
      src/main/java/it/bgates/remotebe/config/SecurityConfig.java
  6. 36 0
      src/main/java/it/bgates/remotebe/controller/BillingsController.java
  7. 51 0
      src/main/java/it/bgates/remotebe/controller/ConfigController.java
  8. 53 1
      src/main/java/it/bgates/remotebe/controller/DeviceController.java
  9. 110 8
      src/main/java/it/bgates/remotebe/controller/PhoneController.java
  10. 26 1
      src/main/java/it/bgates/remotebe/controller/UserController.java
  11. 9 0
      src/main/java/it/bgates/remotebe/controller/beans/ConfigBean.java
  12. 43 0
      src/main/java/it/bgates/remotebe/entities/Billing.java
  13. 26 0
      src/main/java/it/bgates/remotebe/entities/Config.java
  14. 5 3
      src/main/java/it/bgates/remotebe/entities/Customer.java
  15. 25 13
      src/main/java/it/bgates/remotebe/entities/Device.java
  16. 9 2
      src/main/java/it/bgates/remotebe/entities/Phone.java
  17. 1 0
      src/main/java/it/bgates/remotebe/entities/User.java
  18. 22 0
      src/main/java/it/bgates/remotebe/entities/enums/BillingType.java
  19. 7 0
      src/main/java/it/bgates/remotebe/exception/AlreadyPresentException.java
  20. 7 0
      src/main/java/it/bgates/remotebe/exception/InsufficientCreditException.java
  21. 6 0
      src/main/java/it/bgates/remotebe/exception/InvalidBillingTypeException.java
  22. 7 0
      src/main/java/it/bgates/remotebe/exception/NullValueNotAllowed.java
  23. 14 0
      src/main/java/it/bgates/remotebe/repository/BillingRepository.java
  24. 19 0
      src/main/java/it/bgates/remotebe/repository/ConfigRepository.java
  25. 2 4
      src/main/java/it/bgates/remotebe/repository/CustomerRepository.java
  26. 9 2
      src/main/java/it/bgates/remotebe/repository/DeviceRepository.java
  27. 23 0
      src/main/java/it/bgates/remotebe/repository/PhoneRepository.java
  28. 21 0
      src/main/java/it/bgates/remotebe/repository/UserRepository.java
  29. 2 2
      src/main/java/it/bgates/remotebe/service/BaseService.java
  30. 92 0
      src/main/java/it/bgates/remotebe/service/BillingService.java
  31. 105 0
      src/main/java/it/bgates/remotebe/service/ConfigService.java
  32. 1 5
      src/main/java/it/bgates/remotebe/service/CustomerService.java
  33. 225 12
      src/main/java/it/bgates/remotebe/service/DeviceService.java
  34. 206 32
      src/main/java/it/bgates/remotebe/service/PhoneService.java
  35. 0 9
      src/main/java/it/bgates/remotebe/service/QueryParameter.java
  36. 71 7
      src/main/java/it/bgates/remotebe/service/UserService.java
  37. 105 0
      src/main/java/it/bgates/remotebe/service/skebby/SkebbyService.java
  38. 10 2
      src/main/resources/application-dev.properties
  39. 5 1
      src/main/resources/application-prod.properties
  40. 7 1
      src/main/resources/application.properties

+ 5 - 0
pom.xml

@@ -123,6 +123,11 @@
             <version>2.8.14</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5.14</version>
+        </dependency>
 
     </dependencies>
 

+ 30 - 0
src/main/java/it/bgates/remotebe/config/ApplicationConfig.java

@@ -0,0 +1,30 @@
+package it.bgates.remotebe.config;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.core.env.StandardEnvironment;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class ApplicationConfig {
+    private final StandardEnvironment environment;
+
+    public Boolean getBoolean(String configName) {
+        return environment.getProperty(configName, Boolean.class);
+    }
+
+    public String getString(String configName) {
+        return environment.getProperty(configName, String.class);
+    }
+
+    public Integer getInteger(String configName) {
+        return environment.getProperty(configName, Integer.class);
+    }
+
+    public Double getDouble(String configName) {
+        return environment.getProperty(configName, Double.class);
+    }
+
+
+
+}

+ 1 - 0
src/main/java/it/bgates/remotebe/config/BGatesUserDetailService.java

@@ -49,6 +49,7 @@ public class BGatesUserDetailService implements UserDetailsService, Authenticati
                 user.get().getPerson(),
                 user.get().getCustomer(),
                 user.get().getDistributor(),
+                user.get().getGtCoins(),
                 getPrivileges(user.get().getRoles()),
                 roles
         );

+ 3 - 0
src/main/java/it/bgates/remotebe/config/BGatesUserDetails.java

@@ -24,6 +24,7 @@ public class BGatesUserDetails implements UserDetails {
     private Person person;
     private final Customer customer;
     private final Distributor distributor;
+    private final Integer gtCoins;
     private final List<String> privileges;
     private final List<String> roles;
 
@@ -36,6 +37,7 @@ public class BGatesUserDetails implements UserDetails {
             Person person,
             Customer customer,
             Distributor distributor,
+            Integer gtCoins,
             final List<String> privileges,
             final List<String> roles) {
         this.id = id;
@@ -45,6 +47,7 @@ public class BGatesUserDetails implements UserDetails {
         this.person = person;
         this.customer = customer;
         this.distributor = distributor;
+        this.gtCoins = gtCoins;
         this.enabled = enabled;
         this.privileges = Collections.unmodifiableList(privileges);
         this.roles = Collections.unmodifiableList(roles);

+ 17 - 34
src/main/java/it/bgates/remotebe/config/SecurityConfig.java

@@ -1,13 +1,6 @@
 package it.bgates.remotebe.config;
 
-import com.nimbusds.jose.jwk.JWK;
-import com.nimbusds.jose.jwk.JWKSet;
-import com.nimbusds.jose.jwk.RSAKey;
-import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
-import com.nimbusds.jose.jwk.source.JWKSource;
-import com.nimbusds.jose.proc.SecurityContext;
 import lombok.RequiredArgsConstructor;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@@ -16,21 +9,14 @@ import org.springframework.security.config.annotation.authentication.configurati
 import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.crypto.password.NoOpPasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.security.oauth2.jwt.JwtDecoder;
-import org.springframework.security.oauth2.jwt.JwtEncoder;
-import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
-import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 import org.springframework.security.web.authentication.logout.LogoutHandler;
 
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.util.Arrays;
-
 import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;
 
 @Configuration
@@ -61,22 +47,22 @@ public class SecurityConfig {
     public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
 
         http
-                .csrf(csrf -> csrf.disable())
-                .authorizeHttpRequests(req ->
-                        req
-                                .requestMatchers(WHITE_LIST_URL)
-                                .permitAll()
-                                .anyRequest()
-                                .fullyAuthenticated()
-                )
-                .sessionManagement(session -> session.sessionCreationPolicy(STATELESS))
-                .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
-                .logout(logout ->
-                        logout.logoutUrl("/auth/logout")
-                                .addLogoutHandler(logoutHandler)
-                                .logoutSuccessHandler((request, response, authentication) -> SecurityContextHolder.clearContext())
-
-                );
+            .csrf(AbstractHttpConfigurer::disable)
+            .authorizeHttpRequests(req ->
+                    req
+                            .requestMatchers(WHITE_LIST_URL)
+                            .permitAll()
+                            .anyRequest()
+                            .fullyAuthenticated()
+            )
+            .sessionManagement(session -> session.sessionCreationPolicy(STATELESS))
+            .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
+            .logout(logout ->
+                    logout.logoutUrl("/auth/logout")
+                            .addLogoutHandler(logoutHandler)
+                            .logoutSuccessHandler((request, response, authentication) -> SecurityContextHolder.clearContext())
+
+            );
 
 
         return http.build();
@@ -89,7 +75,4 @@ public class SecurityConfig {
         return authenticationConfiguration.getAuthenticationManager();
     }
 
-
-
-
 }

+ 36 - 0
src/main/java/it/bgates/remotebe/controller/BillingsController.java

@@ -0,0 +1,36 @@
+package it.bgates.remotebe.controller;
+
+import it.bgates.remotebe.entities.Billing;
+import it.bgates.remotebe.service.BillingService;
+import lombok.AllArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.security.Principal;
+import java.util.List;
+
+import static org.springframework.http.HttpStatus.PRECONDITION_FAILED;
+
+@RestController
+@AllArgsConstructor
+@RequestMapping("/billings")
+public class BillingsController {
+
+    private final BillingService billingService;
+
+
+    @GetMapping("")
+    private ResponseEntity<List<Billing>> getBillings(Principal principal) {
+        try {
+            List<Billing> billings = billingService.getBillings(principal);
+
+            return ResponseEntity.ok(billings);
+
+        } catch (Exception e) {
+            return ResponseEntity.status(PRECONDITION_FAILED).build();
+        }
+    }
+
+}

+ 51 - 0
src/main/java/it/bgates/remotebe/controller/ConfigController.java

@@ -0,0 +1,51 @@
+package it.bgates.remotebe.controller;
+
+import it.bgates.remotebe.entities.Config;
+import it.bgates.remotebe.exception.PermissionDeniedException;
+import it.bgates.remotebe.exception.UserNotFoundException;
+import it.bgates.remotebe.service.ConfigService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.security.Principal;
+import java.util.List;
+
+import static org.springframework.http.HttpStatus.PRECONDITION_FAILED;
+import static org.springframework.http.HttpStatus.UNAUTHORIZED;
+
+@RestController
+@RequestMapping("/config")
+@RequiredArgsConstructor
+public class ConfigController {
+
+    private final ConfigService configService;
+
+    @GetMapping()
+    private ResponseEntity<List<Config>> getConfig(Principal principal) {
+
+        try {
+            return ResponseEntity.ok(configService.getConfig(principal));
+        } catch(PermissionDeniedException ex) {
+            return ResponseEntity.status(UNAUTHORIZED).build();
+        } catch (UserNotFoundException e) {
+            return ResponseEntity.status(PRECONDITION_FAILED).build();
+        }
+    }
+
+
+
+    @PostMapping("")
+    private ResponseEntity<Double> saveConfig(@RequestBody List<Config> config, Principal principal) {
+
+        try {
+            configService.saveConfig(config, principal);
+        } catch (UserNotFoundException ex) {
+            return ResponseEntity.status(PRECONDITION_FAILED).build();
+        } catch (PermissionDeniedException ex) {
+            return ResponseEntity.status(UNAUTHORIZED).build();
+        }
+
+        return ResponseEntity.ok().build();
+    }
+}

+ 53 - 1
src/main/java/it/bgates/remotebe/controller/DeviceController.java

@@ -2,17 +2,23 @@ package it.bgates.remotebe.controller;
 
 import it.bgates.remotebe.controller.beans.FilterBean;
 import it.bgates.remotebe.entities.Device;
+import it.bgates.remotebe.exception.InsufficientCreditException;
+import it.bgates.remotebe.exception.InvalidBillingTypeException;
+import it.bgates.remotebe.exception.NotFoundException;
 import it.bgates.remotebe.exception.UserNotFoundException;
 import it.bgates.remotebe.service.DeviceService;
-import jakarta.servlet.Filter;
 import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ProblemDetail;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 
 import java.security.Principal;
 import java.util.List;
 
+import static org.springframework.http.HttpStatus.NOT_FOUND;
 import static org.springframework.http.HttpStatus.PRECONDITION_FAILED;
+import static org.springframework.http.ProblemDetail.forStatus;
 
 @RestController
 @RequestMapping("/devices")
@@ -46,11 +52,37 @@ public class DeviceController {
         try {
             Device savedDevice = deviceService.save(device, principal);
             return ResponseEntity.ok().body(savedDevice);
+        } catch (InsufficientCreditException e) {
+            ProblemDetail pd = forStatus(500);
+            pd.setDetail(e.getMessage());
+            return ResponseEntity.of(pd).build();
         } catch (Exception e) {
             return ResponseEntity.badRequest().build();
         }
     }
 
+    @DeleteMapping("/{id}")
+    private ResponseEntity<String> deleteDevice(@PathVariable Integer id, Principal principal) {
+        try {
+            boolean res = deviceService.deleteById(id, principal);
+            if (res) {
+                return ResponseEntity.ok().build();
+            } else {
+                return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
+            }
+        } catch(InsufficientCreditException e) {
+            ProblemDetail pd = forStatus(500);
+            pd.setDetail(e.getMessage());
+            return ResponseEntity.of(pd).build();
+        } catch(InvalidBillingTypeException e) {
+            return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
+        } catch(NotFoundException e) {
+            return ResponseEntity.status(NOT_FOUND).build();
+        } catch(UserNotFoundException ex) {
+            return ResponseEntity.status(PRECONDITION_FAILED).build();
+        }
+    }
+
     @PostMapping("/filter")
     public ResponseEntity<List<Device>> filterDevices(@RequestBody FilterBean filterBean, Principal principal) {
         try {
@@ -59,7 +91,27 @@ public class DeviceController {
         } catch (UserNotFoundException e) {
             return ResponseEntity.status(PRECONDITION_FAILED).build();
         }
+    }
+
+    @PostMapping("/send-config/{id}")
+    private ResponseEntity<Boolean> sendConfig(@PathVariable Integer id, Principal principal)  {
+        try {
+            Device device = deviceService.getDevice(id, principal);
+            if (device == null) return ResponseEntity.notFound().build();
 
+            if (deviceService.sendConfig(principal, device)) {
+                return ResponseEntity.ok(true);
+            } else {
+                return ResponseEntity.status(500).build();
+            }
+
+        } catch (UserNotFoundException e) {
+            return ResponseEntity.status(PRECONDITION_FAILED).build();
+        } catch (Exception e) {
+            ProblemDetail pd = forStatus(500);
+            pd.setDetail(e.getMessage());
+            return ResponseEntity.of(pd).build();
+        }
     }
 
 }

+ 110 - 8
src/main/java/it/bgates/remotebe/controller/PhoneController.java

@@ -2,19 +2,22 @@ package it.bgates.remotebe.controller;
 
 import it.bgates.remotebe.controller.beans.FilterBean;
 import it.bgates.remotebe.entities.Phone;
+import it.bgates.remotebe.exception.InsufficientCreditException;
 import it.bgates.remotebe.exception.NotFoundException;
+import it.bgates.remotebe.exception.PermissionDeniedException;
 import it.bgates.remotebe.exception.UserNotFoundException;
 import it.bgates.remotebe.service.PhoneService;
-import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ProblemDetail;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 
 import java.security.Principal;
 import java.util.List;
 
-import static org.springframework.http.HttpStatus.NOT_FOUND;
-import static org.springframework.http.HttpStatus.PRECONDITION_FAILED;
+import static org.springframework.http.HttpStatus.*;
+import static org.springframework.http.ProblemDetail.forStatus;
 
 @RestController
 @RequestMapping("/phones")
@@ -23,6 +26,14 @@ public class PhoneController {
 
     private final PhoneService phoneService;
 
+    /**
+     * Retrieves a list of phones associated with a specific device ID.
+     *
+     * @param id the unique identifier of the device whose associated phones are to be retrieved
+     * @param principal the security principal of the currently authenticated user
+     * @return a ResponseEntity containing a list of phones associated with the specified device ID;
+     *         returns a 412 status if the user is not found or a 404 status if the device or its phones are not found
+     */
     @GetMapping("/{id}")
     public ResponseEntity<List<Phone>> getDevicePhones(@PathVariable Integer id, Principal principal) {
         try {
@@ -35,21 +46,100 @@ public class PhoneController {
         }
     }
 
+    /**
+     * Adds a new phone entity associated with the authenticated user.
+     *
+     * @param phone the phone entity to be added
+     *              the phone entity should contain the device (at least the "id" field) to
+     *              which the phone will be associated
+     * @param principal  the security principal of the currently authenticated user
+     * @return a ResponseEntity containing the added phone entity if successful;
+     *         returns a 500 status with error details if an InsufficientCreditException occurs,
+     *         a 412 status if the user is not found, or a 500 status for other exceptions
+     **/
     @PostMapping("")
-    public ResponseEntity<Phone> addPhone(@Valid @RequestBody Phone phone, Principal principal) {
-        return ResponseEntity.ok(phoneService.addPhone(phone, principal));
+    public ResponseEntity<Phone> addPhone(@RequestBody Phone phone, Principal principal) {
+        try {
+            return ResponseEntity.ok(phoneService.addPhone(phone, principal));
+        } catch (InsufficientCreditException ex) {
+            ProblemDetail pd = forStatus(500);
+            pd.setDetail(ex.getMessage());
+            return ResponseEntity.of(pd).build();
+        } catch (UserNotFoundException e) {
+            return ResponseEntity.status(PRECONDITION_FAILED).build();
+        } catch (Exception e) {
+            ProblemDetail pd = forStatus(HttpStatus.INTERNAL_SERVER_ERROR);
+            pd.setDetail(e.getMessage());
+            return ResponseEntity.of(pd).build();
+        }
+    }
+
+    /**
+     * Deletes the phone entities associated with a specified id.
+     * The service checks if the current user is authorized to access the device
+     * that has the phone.  Throws exception if not, else mark the phone number
+     * for deletion and synchronization.
+     * On next send-configuration, the phone is removed from the device using
+     * the SMS delete command, and the phone is remove ONLY IF it has
+     * from and to dates set (temporary number).
+     *
+     * @param id the unique identifier of the phone to be deleted
+     * @param principal the security principal of the currently authenticated user
+     * @return a ResponseEntity containing a boolean value indicating the success of the deletion;
+     *         returns a 412 status if the user is not found,
+     *         a 404 status if the device or phones are not found,
+     *         or a 500 status with error details for a permission denial
+     */
+
+    @DeleteMapping("/{id}")
+    public ResponseEntity<Boolean> deleteDevicePhones(@PathVariable Integer id, Principal principal) {
+        try {
+            boolean res = phoneService.deletePhones(id, principal);
+            return ResponseEntity.ok(res);
+        } catch (UserNotFoundException e) {
+            return ResponseEntity.status(PRECONDITION_FAILED).build();
+        } catch(NotFoundException e) {
+            return ResponseEntity.status(NOT_FOUND).build();
+        } catch (PermissionDeniedException e) {
+            ProblemDetail pd = forStatus(HttpStatus.INTERNAL_SERVER_ERROR);
+            pd.setDetail(e.getMessage());
+            return ResponseEntity.of(pd).build();
+        }
     }
 
-    @PostMapping("/filter")
-    public ResponseEntity<List<Phone>> filter(@RequestBody FilterBean filterBean, Principal principal) {
+    /**
+     * Filters a list of phones based on the specified device ID and optional filter criteria.
+     *
+     * @param deviceId the unique identifier of the device to filter phones for
+     * @param filterBean the filter criteria for phones; can be null if no filtering is required
+     * @param principal the security principal of the currently authenticated user
+     * @return a ResponseEntity containing the list of filtered phones if successful
+     */
+    @PostMapping("/filter/{deviceId}")
+    public ResponseEntity<List<Phone>> filter(
+            @PathVariable() Integer deviceId,
+            @RequestBody(required = false) FilterBean filterBean,
+            Principal principal
+    ) {
         try {
-            List<Phone> phones = phoneService.filter(filterBean.getFilter(), principal);
+            String filter = (filterBean != null) ? filterBean.getFilter() : null;
+            List<Phone> phones = phoneService.filter(deviceId, filter, principal);
             return ResponseEntity.ok(phones);
         } catch (UserNotFoundException e) {
             return ResponseEntity.status(PRECONDITION_FAILED).build();
         }
     }
 
+    /**
+     * Deactivates a phone associated with the specified ID.
+     * The service ensures that the authenticated user has the necessary permissions
+     * to deactivate the phone. If successful, the phone will no longer be active.
+     *
+     * @param id the unique identifier of the phone to be deactivated
+     * @param principal the security principal of the currently authenticated user
+     * @return a ResponseEntity containing a boolean value indicating whether the deactivation was successful;
+     *         returns a 412 status if the user is not found, or a 404 status if the phone is not found
+     */
     @GetMapping("/deactivate/{id}")
     public ResponseEntity<Boolean> deactivatePhone(@PathVariable Integer id, Principal principal) {
         try {
@@ -59,9 +149,19 @@ public class PhoneController {
             return ResponseEntity.status(PRECONDITION_FAILED).build();
         } catch (NotFoundException e) {
             return ResponseEntity.status(NOT_FOUND).build();
+        } catch (PermissionDeniedException e) {
+            return ResponseEntity.status(UNAUTHORIZED).build();
         }
     }
 
+    /**
+     * Activates a phone associated with the specified ID.
+     *
+     * @param id         the unique identifier of the phone to be activated.
+     * @param principal  the authenticated user making the request.
+     * @return           a ResponseEntity containing a Boolean indicating the success of the activation,
+     *                   or an appropriate HTTP status if an error occurs.
+     */
     @GetMapping("/activate/{id}")
     public ResponseEntity<Boolean> activatePhone(@PathVariable Integer id, Principal principal) {
         try {
@@ -71,6 +171,8 @@ public class PhoneController {
             return ResponseEntity.status(PRECONDITION_FAILED).build();
         } catch (NotFoundException e) {
             return ResponseEntity.status(NOT_FOUND).build();
+        } catch (PermissionDeniedException e) {
+            return ResponseEntity.status(UNAUTHORIZED).build();
         }
     }
 }

+ 26 - 1
src/main/java/it/bgates/remotebe/controller/UserController.java

@@ -20,7 +20,7 @@ import static org.springframework.http.HttpStatus.*;
 @RestController
 @RequiredArgsConstructor
 @RequestMapping("/users")
-public class UserController {
+public class  UserController {
 
     private final AuthService authService;
     private final UserService userService;
@@ -87,4 +87,29 @@ public class UserController {
         }
     }
 
+    @PostMapping("enable/{id}")
+    public ResponseEntity<Boolean> enableUser(@PathVariable() Integer id, Principal principal) {
+        Boolean result = null;
+        try {
+            result = userService.enableUser(id, principal);
+            return ResponseEntity
+                    .status(OK)
+                    .body(result);
+
+        } catch (UserNotFoundException e) {
+            return ResponseEntity.status(PRECONDITION_FAILED).build();
+        } catch (PermissionDeniedException e) {
+            return ResponseEntity.status(FORBIDDEN).build();
+        }
+    }
+
+    @PostMapping("/add-to-balance")
+    public ResponseEntity<Integer> addToBalance(@RequestBody Integer amount, Principal principal){
+       try {
+           return ResponseEntity.status(OK).body(userService.addToBalance(amount, principal));
+       } catch (UserNotFoundException e) {
+           return ResponseEntity.status(PRECONDITION_FAILED).build();
+       }
+    }
+
 }

+ 9 - 0
src/main/java/it/bgates/remotebe/controller/beans/ConfigBean.java

@@ -0,0 +1,9 @@
+package it.bgates.remotebe.controller.beans;
+
+import lombok.Data;
+
+@Data
+public class ConfigBean {
+    private String name;
+    private Double value;
+}

+ 43 - 0
src/main/java/it/bgates/remotebe/entities/Billing.java

@@ -0,0 +1,43 @@
+package it.bgates.remotebe.entities;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import it.bgates.remotebe.entities.enums.BillingType;
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+
+@Getter
+@Setter
+@Entity
+@Table(name = "billings")
+public class Billing {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Integer id;
+
+    private LocalDateTime billingDate;
+    private BillingType type;
+
+    @Column(length = 200)
+    private String description;
+
+    private Integer gtcoinCost;
+
+    private Double gtcoinPrice;
+
+    private Double totalCost;
+
+    @JsonIgnore
+    @ManyToOne
+    @JoinColumn(name = "user_id", foreignKey = @ForeignKey(name = "FK_USER_BILLING"))
+    private User user;
+
+    @JsonIgnore
+    @ManyToOne
+    @JoinColumn(name = "device_id", foreignKey = @ForeignKey(name = "FK_DEVICE_BILLING"))
+    private Device device;
+
+
+}

+ 26 - 0
src/main/java/it/bgates/remotebe/entities/Config.java

@@ -0,0 +1,26 @@
+package it.bgates.remotebe.entities;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Entity
+@Table(name = "config")
+public class Config {
+    @Id
+    private String name;
+    private String value;
+
+    public static String K_COIN_SMS = "coin_sms";
+    public static String K_COST_DEVICE_CREATION = "device_cost";
+    public static String K_COST_DEVICE_DELETION = "device_deletion";
+    public static String K_COST_DEVICE_RESTORE = "restore_cost";
+    public static String K_COST_ADD_USER = "add_cost";
+    public static String K_COST_DELETE_USER = "delete_cost";
+    public static String K_COST_COIN = "coin_cost";
+
+}

+ 5 - 3
src/main/java/it/bgates/remotebe/entities/Customer.java

@@ -1,7 +1,6 @@
 package it.bgates.remotebe.entities;
 
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import it.bgates.remotebe.entities.enums.CustomerType;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import jakarta.persistence.*;
 import lombok.Getter;
 import lombok.Setter;
@@ -36,7 +35,10 @@ public class Customer {
     @Column(length = 60)
     private String email;
 
-    @OneToMany(mappedBy = "owner")
+    // ignora device.owner in quanto è lo stesso valore di this
+    @JsonIgnoreProperties({"hibernateLazyInitializer", "handler", "owner"})
+    @OneToMany(mappedBy = "owner", fetch = FetchType.LAZY)
     List<Device> devices;
 
+
 }

+ 25 - 13
src/main/java/it/bgates/remotebe/entities/Device.java

@@ -1,13 +1,13 @@
 package it.bgates.remotebe.entities;
 
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import it.bgates.remotebe.entities.base.Person;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import it.bgates.remotebe.entities.enums.DeviceType;
 import jakarta.persistence.*;
 import lombok.Getter;
 import lombok.Setter;
 
 import java.time.LocalDate;
+import java.util.List;
 
 @Getter
 @Setter
@@ -20,21 +20,30 @@ public class Device {
 
     private String serialNumber;
     private String description;
+    private String phoneNumber;
+    private String email;
+    private String firmwareVersion;
+    private String imei;
 
-    @JsonIgnore
+    @JsonIgnoreProperties("distributor")
     @ManyToOne
     @JoinColumn(name = "distributor_id", foreignKey = @ForeignKey(name = "FK_DEVICE_DISTRIBUTOR"))
     private Distributor distributor;
 
+    @ManyToOne(fetch = FetchType.EAGER)
+    @JoinColumn(name = "installer_id", foreignKey = @ForeignKey(name = "FK_DEVICE_INSTALLER"))
+    private Installer installer;
 
-    @JsonIgnore
-    @ManyToOne
+
+
+    @JsonIgnoreProperties("devices")
+    @ManyToOne(fetch = FetchType.EAGER)
     @JoinColumn(name = "owner_id", foreignKey = @ForeignKey(name = "FK_DEVICE_OWNER"))
     private Customer owner;
 
-    @ManyToOne
-    @JoinColumn(name = "installer_id", foreignKey = @ForeignKey(name = "FK_DEVICE_INSTALLER"))
-    private Installer installer;
+
+    @OneToMany(mappedBy = "device", fetch = FetchType.LAZY)
+    List<Phone> phones;
 
     @Column(length = 100)
     private String address;
@@ -51,10 +60,8 @@ public class Device {
 
     private String timezone;
 
-    private Boolean logging;
-    private Integer logSize;
-
     private Integer impulseDuration;
+    private Integer smsCounter;
 
     private Integer replyDelay;
     private String server1;
@@ -62,17 +69,22 @@ public class Device {
 
     private LocalDate simExpiryDate;
 
-    private String vericationSms;
     private String welcomeSms;
+    private String goodbyeSms;
     private String type2OnSms;
     private String type2OffSms;
 
     private String logEmail;
 
     private DeviceType type;
-    private Boolean SyndDatetimeServer;
     private Boolean hideSim;
 
+    // Bluetooth keys
+    private String privateByKey;
+    private String publicByKey;
+    private Integer instCode;
+
+    private LocalDate activationDate;
 
 
 }

+ 9 - 2
src/main/java/it/bgates/remotebe/entities/Phone.java

@@ -1,7 +1,8 @@
 package it.bgates.remotebe.entities;
 
-import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
 import jakarta.persistence.*;
+import jakarta.validation.constraints.NotNull;
 import lombok.Getter;
 import lombok.Setter;
 
@@ -18,11 +19,12 @@ public class Phone {
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Integer id;
 
-    @JsonIgnore
     @ManyToOne
     @JoinColumn(name = "device_id", foreignKey = @ForeignKey(name = "FK_PHONE_DEVICE"))
+    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
     private Device device;
 
+    @NotNull
     private String phoneNumber;
     private String description;
     private Boolean active;
@@ -41,4 +43,9 @@ public class Phone {
     private LocalTime fromTime;
     private LocalTime toTime;
 
+    public Boolean modified;
+    public Boolean synched;
+    public Boolean deleted;
+    public LocalDateTime deletionTime;
+
 }

+ 1 - 0
src/main/java/it/bgates/remotebe/entities/User.java

@@ -28,6 +28,7 @@ public class User {
     @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
     private String password;
     private Boolean enabled;
+    private Integer gtCoins;
 
     @NotNull
     @Column(nullable = false)

+ 22 - 0
src/main/java/it/bgates/remotebe/entities/enums/BillingType.java

@@ -0,0 +1,22 @@
+package it.bgates.remotebe.entities.enums;
+
+public enum BillingType {
+    DEVICE_CREATION("C"),
+    USER_DELETION("D"),
+    DEVICE_DELETE("X"),
+    DEVICE_RESTORE("R"),
+    USER_ADD("A");
+
+
+
+    private final String value;
+
+    BillingType(String value) {
+        this.value = value;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+}

+ 7 - 0
src/main/java/it/bgates/remotebe/exception/AlreadyPresentException.java

@@ -0,0 +1,7 @@
+package it.bgates.remotebe.exception;
+
+public class AlreadyPresentException extends Exception{
+    public AlreadyPresentException(String message) {
+        super(message);
+    }
+}

+ 7 - 0
src/main/java/it/bgates/remotebe/exception/InsufficientCreditException.java

@@ -0,0 +1,7 @@
+package it.bgates.remotebe.exception;
+
+public class InsufficientCreditException extends Exception {
+    public InsufficientCreditException(String message) {
+        super(message);
+    }
+}

+ 6 - 0
src/main/java/it/bgates/remotebe/exception/InvalidBillingTypeException.java

@@ -0,0 +1,6 @@
+package it.bgates.remotebe.exception;
+
+public class InvalidBillingTypeException extends Exception {
+    public InvalidBillingTypeException(String message) {
+    }
+}

+ 7 - 0
src/main/java/it/bgates/remotebe/exception/NullValueNotAllowed.java

@@ -0,0 +1,7 @@
+package it.bgates.remotebe.exception;
+
+public class NullValueNotAllowed extends Exception{
+    public NullValueNotAllowed(String message) {
+        super(message);
+    }
+}

+ 14 - 0
src/main/java/it/bgates/remotebe/repository/BillingRepository.java

@@ -0,0 +1,14 @@
+package it.bgates.remotebe.repository;
+
+import it.bgates.remotebe.entities.Billing;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+
+import java.util.List;
+
+public interface BillingRepository extends JpaRepository<Billing, Integer> {
+
+     @Query("Select b from Billing b where b.user.id=:userId order by b.id desc")
+     List<Billing> findAllByUser(Integer userId);
+
+}

+ 19 - 0
src/main/java/it/bgates/remotebe/repository/ConfigRepository.java

@@ -0,0 +1,19 @@
+package it.bgates.remotebe.repository;
+
+import it.bgates.remotebe.entities.Config;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+
+import java.util.Optional;
+
+public interface ConfigRepository extends JpaRepository<Config, String> {
+
+    @Query("select cast(c.value as int) from Config c where c.name=:name")
+    Integer getCostByName(String name);
+
+    @Query("select cast(c.value as Double) from Config c where c.name=:name")
+    Double getCostByNameAsDouble(String name);
+
+    Optional<Config> findByName(String name);
+
+}

+ 2 - 4
src/main/java/it/bgates/remotebe/repository/CustomerRepository.java

@@ -1,7 +1,6 @@
 package it.bgates.remotebe.repository;
 
 import it.bgates.remotebe.entities.Customer;
-import it.bgates.remotebe.entities.enums.CustomerType;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.Query;
 
@@ -9,10 +8,9 @@ import java.util.List;
 
 public interface CustomerRepository extends JpaRepository<Customer, Integer> {
 
-    @Query("SELECT c from Customer c join fetch Device dev on dev.owner = c order by c.name")
+    @Query("SELECT c from Customer c left join fetch Device dev on dev.owner = c order by c.name")
     List<Customer> findAllByOrderByDescription();
 
-    //@Query("SELECT C from Customer C WHERE C.devices in (select D from Device D where D.installer = :installer)  order by C.name")
     @Query(
             value = "SELECT C.* from customers C WHERE C.ID in (select D.owner_id from devices D where D.installer_id = :installerId)  order by C.name",
             nativeQuery = true
@@ -29,7 +27,7 @@ public interface CustomerRepository extends JpaRepository<Customer, Integer> {
 
  */
     @Query(
-            value = "select c from Customer c join fetch Device dev on dev.owner = c join fetch Distributor d on dev.distributor = d where d.id=:distributorId"
+            value = "select c from Customer c left join fetch Device dev on dev.owner = c join fetch Distributor d on dev.distributor = d where d.id=:distributorId"
     )
     List<Customer> findByDistributor(Integer distributorId);
 

+ 9 - 2
src/main/java/it/bgates/remotebe/repository/DeviceRepository.java

@@ -3,8 +3,9 @@ package it.bgates.remotebe.repository;
 import it.bgates.remotebe.entities.Customer;
 import it.bgates.remotebe.entities.Device;
 import it.bgates.remotebe.entities.Distributor;
-import it.bgates.remotebe.entities.User;
 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
 
 import java.util.List;
 
@@ -12,10 +13,16 @@ public interface DeviceRepository extends JpaRepository<Device, Integer> {
     List<Device>  findAll();
 
     List<Device> findAllByInstallerOrderByDescription(Customer installer);
+
+    @Query("SELECT d from Device d join fetch Customer c on d.owner = c where d.distributor = :distributor order by d.description")
     List<Device> findAllByDistributorOrderByDescription(Distributor distributor);
+
     List<Device> findAllByOwnerOrderByDescription(Customer owner);
 
+    @Query("SELECT d from Device d join fetch Customer c on d.owner = c order by d.description")
     List<Device> findAllByOrderByDescription();
 
-    // List<Device> findAllByInstallerOrderByDescription();
+    @Query("update Device d set d.smsCounter = d.smsCounter + 1 where d.id = :id")
+    @Modifying
+    void incrementSmsCounter(Integer id);
 }

+ 23 - 0
src/main/java/it/bgates/remotebe/repository/PhoneRepository.java

@@ -3,10 +3,33 @@ package it.bgates.remotebe.repository;
 import it.bgates.remotebe.entities.Device;
 import it.bgates.remotebe.entities.Phone;
 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
 
 import java.util.List;
 
 public interface PhoneRepository extends JpaRepository<Phone, Integer> {
 
     List<Phone> findByDevice(Device device);
+
+    @Query("select count(p) from Phone p where p.device.id = :deviceId and p.phoneNumber = :phoneNumber")
+    int countPhones(Integer deviceId, String phoneNumber);
+
+    @Query("select p from Phone p where p.device.id = :deviceId and p.deleted  and p.modified")
+    List<Phone> findDeletedPhoneByDevice(Integer deviceId);
+
+
+    @Query("select p from Phone p where p.device.id = :deviceId and not p.deleted and p.modified and not p.synched and p.fromDate is null and p.toDate is null")
+    List<Phone> findModifiedPhoneByDevice(Integer deviceId);
+
+    @Query("update Phone p set p.modified = false, p.synched = true where p.id = :id")
+    @Modifying
+    void clearUserModifiedFlags(Integer id);
+
+
+    @Query(
+            value="select p.* from phones p where p.device_id = :deviceId and (p.deleted = 0 or (p.deleted = 1 and  p.synched = 0))",
+            nativeQuery = true
+    )
+    List<Phone> findByDeviceId(Integer deviceId);
 }

+ 21 - 0
src/main/java/it/bgates/remotebe/repository/UserRepository.java

@@ -3,6 +3,8 @@ package it.bgates.remotebe.repository;
 import it.bgates.remotebe.entities.Distributor;
 import it.bgates.remotebe.entities.User;
 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
 
 import java.util.List;
 import java.util.Optional;
@@ -12,4 +14,23 @@ public interface UserRepository extends JpaRepository<User, Integer> {
 
     List<User> findAllByOrderByUsername();
     List<User> findAllByDistributorOrderByUsername(Distributor distributor);
+
+    @Query("select coalesce(u.gtCoins,0) from User u where u.id=:userId")
+    Integer getGtCoins(Integer userId);
+
+    @Modifying
+    @Query("update User u set u.gtCoins = coalesce(u.gtCoins, 0) + :amount where u.id=:userId")
+    void addToGtCoins(Integer userId, Integer amount);
+
+    @Modifying
+    @Query("update User u set u.gtCoins = coalesce(u.gtCoins, 0) - :amount where u.id=:userId")
+    void detractToGtCoins(Integer userId, Integer amount);
+
+    @Modifying
+    @Query("update User u set u.enabled = true where u.id=:id")
+    void setEnabled(Integer id);
+
+    @Modifying
+    @Query("update User u set u.enabled = false where u.id=:id")
+    void setDisabled(Integer id);
 }

+ 2 - 2
src/main/java/it/bgates/remotebe/service/BaseService.java

@@ -1,7 +1,6 @@
 package it.bgates.remotebe.service;
 
 import it.bgates.remotebe.config.BGatesUserDetails;
-import it.bgates.remotebe.entities.Role;
 import it.bgates.remotebe.entities.enums.BGatesUserType;
 import it.bgates.remotebe.exception.UserNotFoundException;
 import org.springframework.data.domain.Pageable;
@@ -18,7 +17,7 @@ public class BaseService {
     protected BGatesUserDetails userDetails;
 
     protected BGatesUserDetails getUserDetails(Principal principal) throws UserNotFoundException {
-        userDetails = (BGatesUserDetails)  ((UsernamePasswordAuthenticationToken) principal).getPrincipal();;
+        userDetails = (BGatesUserDetails)  ((UsernamePasswordAuthenticationToken) principal).getPrincipal();
         if (userDetails == null) {
             throw new UserNotFoundException("User not found");
         }
@@ -88,4 +87,5 @@ public class BaseService {
         }
         return defaultSortField;
     }
+
 }

+ 92 - 0
src/main/java/it/bgates/remotebe/service/BillingService.java

@@ -0,0 +1,92 @@
+package it.bgates.remotebe.service;
+
+import it.bgates.remotebe.entities.Billing;
+import it.bgates.remotebe.entities.Device;
+import it.bgates.remotebe.entities.User;
+import it.bgates.remotebe.entities.enums.BillingType;
+import it.bgates.remotebe.exception.InsufficientCreditException;
+import it.bgates.remotebe.exception.InvalidBillingTypeException;
+import it.bgates.remotebe.exception.UserNotFoundException;
+import it.bgates.remotebe.repository.BillingRepository;
+import it.bgates.remotebe.repository.ConfigRepository;
+import jakarta.persistence.EntityManager;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.security.Principal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+public class BillingService extends BaseService {
+
+    private final BillingRepository billingRepository;
+    private final ConfigService configService;
+    private final UserService userService;
+    private final EntityManager entityManager;
+    private final ConfigRepository configRepository;
+
+
+    ///  obsolete
+    public boolean sufficientBalance(Principal principal, Integer operationPrice) throws UserNotFoundException {
+        return true;
+/*
+        getUserDetails(principal);
+        Integer userCoinAmount = userService.getGtCoins(userDetails.getId());
+        return userCoinAmount.compareTo( operationPrice )  >= 0;
+ */
+    }
+
+    public Integer addBilling(Principal principal, Device device, BillingType billingType, String description)
+            throws
+            UserNotFoundException,
+            InsufficientCreditException,
+            InvalidBillingTypeException {
+
+
+        getUserDetails(principal);
+
+/*
+        Integer coinsNeeded = configService.getBillingTypeCost(billingType);
+        Double coinCost = configService.getGtCoinCost();
+        Double cost = coinsNeeded * coinCost;
+
+
+        Integer userCoinAmount = userService.getGtCoins(userDetails.getId());
+
+        if (userCoinAmount.compareTo(coinsNeeded) < 0) {
+            throw new InsufficientCreditException("Saldo GT-Coins insufficiente per effettuare l'operazione");
+        }
+*/
+        User user = getCurrentUser();
+
+        Billing billing = new Billing();
+
+        billing.setUser(user);
+        billing.setDevice(device);
+        billing.setType(billingType);
+        billing.setBillingDate(LocalDateTime.now());
+        billing.setDescription(description);
+        billing.setGtcoinPrice(0.0); // prezzo per gt-coin al momento dell'addebito
+        billing.setGtcoinCost(0);  // numero di gt-coin necessari per l'operazione
+        billing.setTotalCost(0.0);          // costo totale in euro
+        billingRepository.save(billing);
+
+        return 1;
+        //return userService.subtractGtCoins(user, coinsNeeded);
+    }
+
+
+    public User getCurrentUser() {
+        return userService.getById(userDetails.getId()).orElse(null);
+    }
+
+    public List<Billing> getBillings(Principal principal) throws UserNotFoundException {
+        getUserDetails(principal);
+
+        List<Billing> billings = billingRepository.findAllByUser(userDetails.getId());
+
+        return billings;
+    }
+}

+ 105 - 0
src/main/java/it/bgates/remotebe/service/ConfigService.java

@@ -0,0 +1,105 @@
+package it.bgates.remotebe.service;
+
+import it.bgates.remotebe.controller.beans.ConfigBean;
+import it.bgates.remotebe.entities.Config;
+import it.bgates.remotebe.entities.enums.BillingType;
+import it.bgates.remotebe.exception.NotFoundException;
+import it.bgates.remotebe.exception.PermissionDeniedException;
+import it.bgates.remotebe.exception.UserNotFoundException;
+import it.bgates.remotebe.repository.ConfigRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.web.bind.annotation.RequestBody;
+
+import java.security.Principal;
+import java.util.List;
+import java.util.Optional;
+
+@Service
+@RequiredArgsConstructor
+public class ConfigService extends BaseService {
+
+    private final ConfigRepository configRepository;
+
+    public Integer getCost(String costType) {
+        return configRepository.getCostByName(costType);
+    }
+
+    public Integer getBillingTypeCost(BillingType billingType) {
+        Integer cost = null;
+        return switch (billingType) {
+            case DEVICE_CREATION -> configRepository.getCostByName(Config.K_COST_DEVICE_CREATION);
+            case DEVICE_DELETE -> configRepository.getCostByName(Config.K_COST_DEVICE_DELETION);
+            case DEVICE_RESTORE -> configRepository.getCostByName(Config.K_COST_DEVICE_RESTORE);
+            case USER_ADD -> configRepository.getCostByName(Config.K_COST_ADD_USER);
+            case USER_DELETION -> configRepository.getCostByName(Config.K_COST_DELETE_USER);
+            default -> null;
+        };
+    }
+
+    public Double getGtCoinCost() {
+        return configRepository.getCostByNameAsDouble(Config.K_COST_COIN);
+    }
+
+    public Double setGtCoinCost(Double cost) throws NotFoundException {
+        Optional<Config> config = configRepository.findByName(Config.K_COST_COIN);
+        if(config.isEmpty()){
+            throw new NotFoundException("Parametro " + Config.K_COST_COIN+ " non trovato");
+        }
+        config.get().setValue(cost.toString());
+        configRepository.save(config.get());
+        return cost;
+    }
+
+    public void setConfig(ConfigBean configBean, Principal principal) throws UserNotFoundException, PermissionDeniedException {
+        getUserDetails(principal);
+
+        if (!isSuperUser()) {
+            throw new PermissionDeniedException("Accesso negato");
+        }
+
+        Config config = configRepository.findByName(configBean.getName()).orElse(null);
+        if (config != null) {
+            config.setValue(configBean.getValue().toString());
+            configRepository.save(config);
+        }
+
+    }
+
+    public Double getConfigAsDouble(String configName, Principal principal) throws UserNotFoundException, PermissionDeniedException {
+        getUserDetails(principal);
+
+        if (!isSuperUser()) {
+            throw new PermissionDeniedException("Accesso negato");
+        }
+
+        Config config = configRepository.findByName(configName).orElse(null);
+        if (config != null) {
+            return Double.parseDouble(config.getValue());
+        }
+
+        return null;
+    }
+
+    public List<Config> getConfig(Principal principal) throws UserNotFoundException, PermissionDeniedException {
+        getUserDetails(principal);
+
+        if (!isSuperUser()) {
+            throw new PermissionDeniedException("Accesso negato");
+        }
+
+        return configRepository.findAll();
+
+    }
+
+    public void saveConfig(@RequestBody List<Config> config, Principal principal) throws UserNotFoundException, PermissionDeniedException {
+        getUserDetails(principal);
+
+        if (!isSuperUser()) {
+            throw new PermissionDeniedException("Accesso negato");
+        }
+
+        configRepository.saveAll(config);
+    }
+
+}

+ 1 - 5
src/main/java/it/bgates/remotebe/service/CustomerService.java

@@ -1,9 +1,7 @@
 package it.bgates.remotebe.service;
 
-import it.bgates.remotebe.config.BGatesUserDetails;
 import it.bgates.remotebe.entities.Customer;
 import it.bgates.remotebe.entities.User;
-import it.bgates.remotebe.entities.enums.CustomerType;
 import it.bgates.remotebe.exception.NotFoundException;
 import it.bgates.remotebe.exception.PermissionDeniedException;
 import it.bgates.remotebe.exception.UserNotFoundException;
@@ -21,6 +19,7 @@ public class CustomerService extends BaseService {
     private final CustomerRepository customerRepository;
     private final UserService userService;
 
+
     public List<Customer> getAllCustomers(Principal principal) throws UserNotFoundException {
         getUserDetails(principal);
 
@@ -48,9 +47,6 @@ public class CustomerService extends BaseService {
         if (isUser()) {
             throw new PermissionDeniedException("Accesso negato");
         }
-        if (isDistributor()) {
-
-        }
 
         return customerRepository.save(customer);
     }

+ 225 - 12
src/main/java/it/bgates/remotebe/service/DeviceService.java

@@ -1,20 +1,24 @@
 package it.bgates.remotebe.service;
 
 import it.bgates.remotebe.config.BGatesUserDetails;
-import it.bgates.remotebe.entities.Customer;
-import it.bgates.remotebe.entities.Device;
-import it.bgates.remotebe.entities.Distributor;
-import it.bgates.remotebe.entities.User;
+import it.bgates.remotebe.entities.*;
+import it.bgates.remotebe.entities.enums.BillingType;
+import it.bgates.remotebe.exception.InsufficientCreditException;
+import it.bgates.remotebe.exception.InvalidBillingTypeException;
+import it.bgates.remotebe.exception.NotFoundException;
 import it.bgates.remotebe.exception.UserNotFoundException;
 import it.bgates.remotebe.repository.DeviceRepository;
 import it.bgates.remotebe.repository.UserRepository;
+import it.bgates.remotebe.service.skebby.SkebbyService;
 import jakarta.persistence.EntityManager;
-import jakarta.persistence.Query;
 import jakarta.persistence.TypedQuery;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
+import java.io.IOException;
 import java.security.Principal;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
 
@@ -25,7 +29,11 @@ public class DeviceService extends BaseService {
     private final DeviceRepository deviceRepository;
     private final UserRepository userRepository;
     private final UserService userService;
+    private final BillingService billingService;
+    private final ConfigService configService;
+    private final PhoneService phoneService;
     private final EntityManager entityManager;
+    private final SkebbyService skebbyService;
 
     /***
      * returns the list of devices accessible the currently logged user
@@ -84,14 +92,14 @@ public class DeviceService extends BaseService {
 
 
         List<String> params = buildFilter(filterBuilder, filter,  new String[] {
-                "serialNumber",
-                "description",
-                "owner.description",
-                "owner.name"
+                "d.serialNumber",
+                "d.description",
+                "o.description",
+                "o.name"
         });
 
 
-        String sql = "SELECT d FROM Device d " + filterBuilder.toString();
+        String sql = "SELECT d FROM Device d left join fetch d.owner o " + filterBuilder.toString();
 
         TypedQuery<Device> query = entityManager.createQuery(sql, Device.class);
         for (int i = 0; i < params.size(); i++) {
@@ -116,9 +124,18 @@ public class DeviceService extends BaseService {
 
     }
 
-    public Device save(Device device, Principal principal) throws UserNotFoundException {
+    @Transactional
+    public Device save(Device device, Principal principal) throws UserNotFoundException, InsufficientCreditException, InvalidBillingTypeException {
         getUserDetails(principal);
 
+        Integer deviceId = device.getId();
+
+        if (deviceId == null) {
+            if (!billingService.sufficientBalance(principal, configService.getBillingTypeCost(BillingType.USER_ADD))) {
+                throw new InsufficientCreditException("Saldo GT-Coins insufficiente per effettuare l'operazione");
+            }
+        }
+
         if (isDistributor()) {
             Optional<User> user = userService.getById(userDetails.getId());
             if (user.isEmpty()) {
@@ -127,8 +144,204 @@ public class DeviceService extends BaseService {
             device.setDistributor(user.get().getDistributor());
         }
 
-        return deviceRepository.save(device);
+        if (!billingService.sufficientBalance(principal, configService.getBillingTypeCost(BillingType.DEVICE_CREATION))) {
+            throw new InsufficientCreditException("Saldo GT-Coins insufficiente per effettuare l'operazione");
+        }
+
+
+        Device deviceSaved = deviceRepository.save(device);
+
+        if (deviceId == null) {
+            billingService.addBilling(principal, device, BillingType.DEVICE_CREATION, String.format(
+                    "Aggiunto dispositivo %d - %s", device.getId(), device.getDescription()
+            ));
+        }
+
+
+        return deviceSaved;
+    }
+
+
+    @Transactional
+    public boolean deleteById(Integer id, Principal principal) throws UserNotFoundException, NotFoundException, InsufficientCreditException, InvalidBillingTypeException {
+        getUserDetails(principal);
+        Device device = deviceRepository.findById(id).orElse(null);
+        if (device == null) {
+            throw new NotFoundException("Dispositivo non trovato");
+        }
+
+        if (!billingService.sufficientBalance(principal, configService.getBillingTypeCost(BillingType.DEVICE_DELETE))) {
+            throw new InsufficientCreditException("Saldo GT-Coins insufficiente per effettuare l'operazione");
+        }
+
+        deviceRepository.delete(device);
+
+        billingService.addBilling(principal, device, BillingType.DEVICE_CREATION, String.format(
+                "Rimosso dispositivo %d - %s", id, device.getDescription()
+        ));
+
+        return true;
+
+    }
+
+    @Transactional
+    public boolean sendConfig(Principal principal, Device device) throws Exception {
+
+        // recupera lista di numeri cancellati e modificati
+        List<Phone> deletedPhones = phoneService.getDeletedPhones(device);
+        List<Phone> modifiedPhones = phoneService.getModifiedPhones(device);
+
+        // controlla se ci sono modifiche da inviare
+        if (deletedPhones.isEmpty() && modifiedPhones.isEmpty()) {
+            throw new Exception("Non ci sono modifiche da inviare");
+        }
+
+        int smsCost = configService.getCost(Config.K_COIN_SMS);
+        int coinsForDeletion = configService.getCost(Config.K_COST_DELETE_USER);
+        int coinsForAddition = configService.getCost(Config.K_COST_ADD_USER);
+
+        Integer neededCoins = deletedPhones.size() * coinsForDeletion + modifiedPhones.size() * coinsForAddition;
+
+        boolean enoughCoins = billingService.sufficientBalance(principal, neededCoins);
+
+        if (!enoughCoins) {
+            throw new InsufficientCreditException(
+                    String.format("Saldo GT-Coins insufficiente per effettuare l'operazione.  Sono richiesti %d coin.", neededCoins));
+        }
+
+
+        int sentCount = 0;
+        int totalCost = 0;
+
+        if (!sendDeletedUsers(principal, device, deletedPhones, coinsForDeletion)) {
+            throw new RuntimeException("Errore durante l'invio di SMS per cancellazione numeri");
+        }
+
+        if (!sendModifiedUsers(principal, device, modifiedPhones, coinsForAddition)) {
+            throw new RuntimeException("Errore durante l'invio di SMS per aggiunta numeri");
+        }
+
+        return true;
+
+    }
+
+    private boolean sendModifiedUsers(Principal principal, Device device, List<Phone> modifiedPhones, int coinsForAddition) throws UserNotFoundException, InvalidBillingTypeException, InsufficientCreditException, IOException {
+
+        StringBuilder smsText = new StringBuilder(device.getPassword() + ",MI");
+        int numberCount = 0;
+
+        List<Phone> sentPhones = new ArrayList<>();
+
+        for(Phone phone: modifiedPhones) {
+
+            smsText.append(",").append(phone.getPhoneNumber().replaceAll(" ", ""));
+            numberCount++;
+            sentPhones.add(phone);
+
+            if (numberCount >= 9) {
+                String  response = skebbyService.sendSms(device.getPhoneNumber(), smsText.toString());
+                if (response == null) {
+                    return false;
+                }
+                // skebbyService.sendSms(device.getPhoneNumber(), smsText.toString());
+                addBilledPhones(principal, device, sentPhones, coinsForAddition);
+                incrementSmsCounter(device);
+
+                // clear sent phones flags
+                for(Phone sentPhone: sentPhones) {
+                    if (sentPhone.getFromDate() != null && sentPhone.getToDate() != null) {
+                        phoneService.delete(sentPhone.getId());
+                    } else {
+                        phoneService.clearUserModifiedFlags(sentPhone.getId());
+                    }
+                }
+
+                sentPhones.clear();
+
+                smsText = new StringBuilder(device.getPassword() + ",MI");
+                numberCount = 0;
+            }
+
+        }
+        if (!smsText.toString().endsWith("MI")) {
+            String  response = skebbyService.sendSms(device.getPhoneNumber(), smsText.toString());
+            // String response = skebbyService.skebbyGatewaySendSMS(device.getPhoneNumber(), smsText.toString(), "send_sms_classic", null, null);
+            if (response != null) {
+                addBilledPhones(principal, device, sentPhones, coinsForAddition);
+                incrementSmsCounter(device);
+
+                // clear sent phones flags
+                for(Phone sentPhone: sentPhones) {
+                    if (sentPhone.getFromDate() != null && sentPhone.getToDate() != null) {
+                        phoneService.delete(sentPhone.getId());
+                    } else {
+                        phoneService.clearUserModifiedFlags(sentPhone.getId());
+                    }
+                }
+
+            } else {
+                return false;
+            }
+
+
+        }
+
+        return true;
+    }
+
+    private boolean sendDeletedUsers(Principal principal, Device device, List<Phone> deletedPhones, int coinsForDeletion) throws UserNotFoundException, InvalidBillingTypeException, InsufficientCreditException {
+
+        User user = getCurrentUser();
+
+        for (Phone phone : deletedPhones) {
+
+            String smsText = device.getPassword() + ",D," + phone.getPhoneNumber();
+
+            if (skebbyService.sendSms(device.getPhoneNumber(), smsText) == null) {
+                return false;
+            }
+
+            String msg = String.format("Eliminato numero %s", phone.getPhoneNumber());
+            incrementSmsCounter(device);
+
+            billingService.addBilling(principal,device, BillingType.USER_DELETION, msg);
+
+            // se era un numero con scadenza, eliminalo, altrimenti marcalo come sincronizzato
+            if (phone.getFromDate() != null && phone.getToDate() != null) {
+                phoneService.delete(phone.getId());
+            } else {
+                phoneService.clearUserModifiedFlags(phone.getId());
+            }
+
+            userService.subtractGtCoins(user, coinsForDeletion);
+
+        }
+        return true;
+    }
+
+
+
+    private void addBilledPhones(Principal principal, Device device, List<Phone> phones, int coinsNeeded) throws UserNotFoundException, InvalidBillingTypeException, InsufficientCreditException {
+
+        for(Phone phone: phones) {
+            String msg = "Aggiunto numero";
+            if (phone.getFromDate() != null && phone.getToDate() != null) {
+                msg += " con scadenza ";
+            }
+            msg += phone.getPhoneNumber();
+
+            billingService.addBilling(principal, device, BillingType.USER_ADD, msg);
+            userService.subtractGtCoins(getCurrentUser(), coinsNeeded);
+        }
     }
 
 
+    public User getCurrentUser() {
+        return userService.getById(userDetails.getId()).orElse(null);
+    }
+
+    public void incrementSmsCounter(Device device) {
+        deviceRepository.incrementSmsCounter(device.getId());
+    }
+
 }

+ 206 - 32
src/main/java/it/bgates/remotebe/service/PhoneService.java

@@ -3,50 +3,100 @@ package it.bgates.remotebe.service;
 import it.bgates.remotebe.entities.Device;
 import it.bgates.remotebe.entities.Phone;
 import it.bgates.remotebe.entities.User;
-import it.bgates.remotebe.exception.NotFoundException;
-import it.bgates.remotebe.exception.UserNotFoundException;
+import it.bgates.remotebe.exception.*;
 import it.bgates.remotebe.repository.PhoneRepository;
 import it.bgates.remotebe.repository.UserRepository;
 import jakarta.persistence.EntityManager;
 import jakarta.persistence.TypedQuery;
 import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
-import org.aspectj.weaver.ast.Not;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.security.Principal;
 import java.time.LocalDateTime;
 import java.util.List;
 import java.util.Optional;
 
-import static org.springframework.data.jpa.domain.AbstractPersistable_.id;
-
 @Service
 @RequiredArgsConstructor
-public class PhoneService extends BaseService{
+public class PhoneService extends BaseService {
     private final PhoneRepository phoneRepository;
-    private final DeviceService deviceService;
     private final UserRepository userRepository;
     private final EntityManager entityManager;
 
+    // private final DeviceService deviceService;
+    // private final BillingService billingService;
+    //private final ConfigService configService;
+
     public List<Phone> getPhonesByDeviceId(Integer deviceId, Principal principal) throws UserNotFoundException, NotFoundException {
+    /*
         Device device = deviceService.getDevice(deviceId, principal);
 
         if (device == null) {
             throw new NotFoundException("Device not found");
         }
 
-        return phoneRepository.findByDevice(device);
+     */
+
+        return phoneRepository.findByDeviceId(deviceId);
     }
 
-    public Phone addPhone(@Valid Phone phone, Principal principal) {
+    @Transactional
+    public Phone addPhone(@Valid Phone phone, Principal principal)
+            throws UserNotFoundException,
+            InvalidBillingTypeException,
+            InsufficientCreditException,
+            AlreadyPresentException, NullValueNotAllowed {
+
+        if (phone.getDevice() == null) {
+            throw new NullValueNotAllowed("Dispositivo non specificato");
+        }
+
+        // del numero di telefono tieni solo le cifre e il carattere +
+        phone.setPhoneNumber(phone.getPhoneNumber().replaceAll("[^0-9+]", ""));
+
         if (phone.getId() == null) {
+
+            /*
+            if (!billingService.sufficientBalance(principal, configService.getBillingTypeCost(BillingType.USER_ADD))) {
+                throw new InsufficientCreditException("Saldo GT-Coins insufficiente per effettuare l'operazione");
+            }
+
+             */
+
+            // check if the same phone number is already present in the device
+            int cnt = phoneRepository.countPhones(phone.getDevice().getId(), phone.getPhoneNumber());
+            if (cnt > 9) {
+                throw new AlreadyPresentException("Numero di telefono già presente nel dispositivo");
+            }
+
             phone.setCreateDate(LocalDateTime.now());
         }
-        return phoneRepository.save(phone);
+
+        if (phone.getDeleted() == null) {
+            phone.setDeleted(false);
+        }
+
+        phone.setModified(true);
+        phone.setSynched(false);
+
+        Phone addedPhone = phoneRepository.save(phone);
+
+        /*
+        billingService.addBilling(principal, phone.getDevice(), BillingType.USER_ADD,
+            String.format(
+                "Aggiunto numero %s", phone.getPhoneNumber()
+            )
+        );
+
+         */
+
+
+        return addedPhone;
     }
 
-    public List<Phone> filter(String filter, Principal principal) throws UserNotFoundException {
+    public List<Phone> filter(Integer deviceId, String filter, Principal principal) throws UserNotFoundException {
         getUserDetails(principal);
 
         Optional<User> user = userRepository.findByUsername(userDetails.getUsername());
@@ -59,28 +109,30 @@ public class PhoneService extends BaseService{
         String join = "";
 
         if (isSuperUser()) {
-             filterBuilder.append("WHERE true");
+            filterBuilder.append("WHERE true ");
         } else if (isDistributor()) {
-            filterBuilder.append("join device d on d.id = p.device.id");
+            filterBuilder.append("join device d on d.id = p.device.id ");
             filterBuilder.append("WHERE d.distributor = :distributor");
         } else if (isUser()) {
-            filterBuilder.append("join device d on d.id = p.device.id");
-            filterBuilder.append("join p.device.owner o on o.id = p.device.owner.id");
-            filterBuilder.append("WHERE o.id=:ownerId");
-
+            filterBuilder.append("join device d on d.id = p.device.id ");
+            filterBuilder.append("join p.device.owner o on o.id = p.device.owner.id ");
+            filterBuilder.append("WHERE o.id=:ownerId ");
         } else {
-            filterBuilder.append("WHERE false");
+            filterBuilder.append("WHERE false ");
         }
 
+        if (deviceId != null) {
+            filterBuilder.append(join).append("and p.device.id=:deviceId ");
+        }
 
-        List<String> params = buildFilter(filterBuilder, filter,  new String[]{
-                "phoneNumber",
-                "description"
+        List<String> params = buildFilter(filterBuilder, filter, new String[]{
+                "p.phoneNumber",
+                "p.description"
         });
 
-        String sql = "SELECT p FROM Phone p" + System.lineSeparator() +
+        String sql = "SELECT p FROM Phone p " + System.lineSeparator() +
                 filterBuilder + System.lineSeparator() +
-                "ORDER BY p.description";
+                " ORDER BY p.description";
 
         TypedQuery<Phone> query = entityManager.createQuery(sql, Phone.class);
         for (int i = 0; i < params.size(); i++) {
@@ -93,35 +145,157 @@ public class PhoneService extends BaseService{
             query.setParameter("ownerId", user.get().getId());
         }
 
+        if (deviceId != null) {
+            query.setParameter("deviceId", deviceId);
+        }
+
         List<Phone> phones = query.getResultList();
         return phones;
 
-
     }
 
-    public Boolean deactivatePhone(Integer phoneId, Principal principal) throws NotFoundException, UserNotFoundException {
+    public Boolean deactivatePhone(Integer phoneId, Principal principal) throws NotFoundException, UserNotFoundException, PermissionDeniedException {
         getUserDetails(principal);
 
+        Optional<User> user = userRepository.findByUsername(userDetails.getUsername());
+        if (user.isEmpty()) {
+            throw new UserNotFoundException("Utente non trovato");
+        }
+
+
+        Phone phoneToDeactivate = checkDeviceAccessibleToUser(phoneId, user.get());
+
+
         Optional<Phone> phone = phoneRepository.findById(phoneId);
         if (phone.isPresent()) {
             phone.get().setActive(false);
+            phone.get().setSynched(false);
+            phone.get().setModified(true);
             phoneRepository.save(phone.get());
             return true;
         }
         throw new NotFoundException("Telefono non trovato");
     }
 
-    public Boolean activatePhone(Integer phoneId, Principal principal) throws NotFoundException, UserNotFoundException {
+    /**
+     * Marks the phone for deletion after verifying user access and permissions.
+     *
+     * @param id The ID of the phone to be deleted.
+     * @param principal The security principal of the currently authenticated user.
+     * @return A boolean value indicating whether the phone was successfully marked as deleted.
+     * @throws UserNotFoundException If the authenticated user could not be found.
+     * @throws PermissionDeniedException If the user does not have permission to access the phone.
+     * @throws NotFoundException If the phone with the specified ID does not exist.
+     */
+    public boolean deletePhones(Integer id, Principal principal) throws UserNotFoundException, PermissionDeniedException, NotFoundException {
         getUserDetails(principal);
 
-        Optional<Phone> phone = phoneRepository.findById(phoneId);
-        if (phone.isPresent()) {
-            phone.get().setActive(true);
-            phoneRepository.save(phone.get());
-            return true;
+        Optional<User> user = userRepository.findByUsername(userDetails.getUsername());
+        if (user.isEmpty()) {
+            throw new UserNotFoundException("Utente non trovato");
         }
-        throw new NotFoundException("Telefono non trovato");
+
+        if (phoneRepository.findById(id).isEmpty()) {
+            throw new NotFoundException("Telefono non trovato");
+        }
+
+        Phone phoneToDelete = checkDeviceAccessibleToUser(id, user.get());
+
+        phoneToDelete.setDeleted(true);
+        phoneToDelete.setSynched(false);
+        phoneToDelete.setDeletionTime(LocalDateTime.now());
+        phoneRepository.save(phoneToDelete);
+
+        return true;
+
     }
 
 
+    /**
+     * Checks if the device that has the phone is accessible to the given user based on their role and permissions.
+     * This method executes a query to retrieve a phone entity based on the user's access level
+     * and the provided ID. If the user does not have permission to access the device,
+     * a {@link PermissionDeniedException} is thrown.
+     *
+     * @param id the ID of the phone to be checked.
+     * @param user an {@code Optional<User>} representing the current logged-in user requesting access.
+     *             The user parameter is required to determine the level of permissions.
+     * @return the {@code Phone} entity that the user has access to if permitted.
+     * @throws PermissionDeniedException if the user is not allowed to manage the specified phone.
+     */
+    private Phone checkDeviceAccessibleToUser(Integer id, User user) throws PermissionDeniedException {
+        // Check if user has access to phone
+        StringBuilder filterBuilder = new StringBuilder();
+
+        String join = "";
+
+        if (isSuperUser()) {
+            filterBuilder.append("WHERE phone.id = :id ");
+        } else if (isDistributor()) {
+            filterBuilder.append("join device d on d.id = phone.device.id ");
+            filterBuilder.append("WHERE d.distributor = :distributor and phone.id = :id");
+        } else if (isUser()) {
+            filterBuilder.append("join device d on d.id = phone.device.id ");
+            filterBuilder.append("join phone.device.owner o on o.id = phone.device.owner.id ");
+            filterBuilder.append("WHERE o.id=:ownerId and phone.id=:id ");
+        } else {
+            filterBuilder.append("WHERE false ");
+        }
+
+        String sql = "SELECT phone FROM Phone phone " + System.lineSeparator() +
+                filterBuilder + System.lineSeparator();
+
+        TypedQuery<Phone> query = entityManager.createQuery(sql, Phone.class);
+        query.setParameter("id", id);
+
+        if (isDistributor()) {
+            query.setParameter("distributor", user.getDistributor());
+        } else if (isUser()) {
+            query.setParameter("ownerId", user.getId());
+        }
+
+        List<Phone> phones = query.getResultList();
+        if (phones.isEmpty()) {
+            throw new PermissionDeniedException("Utente non autorizzato a gestire il telefono specificato");
+        }
+        return phones.getFirst();
+    }
+
+    public Boolean activatePhone(Integer phoneId, Principal principal) throws NotFoundException, UserNotFoundException, PermissionDeniedException {
+        getUserDetails(principal);
+
+        Optional<User> user = userRepository.findByUsername(userDetails.getUsername());
+        if (user.isEmpty()) {
+            throw new UserNotFoundException("Utente non trovato");
+        }
+
+        if (phoneRepository.findById(phoneId).isEmpty()) {
+            throw new NotFoundException("Telefono non trovato");
+        }
+
+        Phone phone = checkDeviceAccessibleToUser(phoneId, user.get());
+
+        phone.setActive(true);
+        phoneRepository.save(phone);
+        return true;
+    }
+
+    public List<Phone> getDeletedPhones(Device device) {
+        return phoneRepository.findDeletedPhoneByDevice(device.getId());
+    }
+
+    public List<Phone> getModifiedPhones(Device device) {
+        return phoneRepository.findModifiedPhoneByDevice(device.getId());
+    }
+
+    public void delete(Integer id) {
+        phoneRepository.deleteById(id);
+    }
+
+    public void clearUserModifiedFlags(Integer id) {
+        phoneRepository.clearUserModifiedFlags(id);
+    }
+
 }
+
+

+ 0 - 9
src/main/java/it/bgates/remotebe/service/QueryParameter.java

@@ -1,9 +0,0 @@
-package it.bgates.remotebe.service;
-
-import lombok.Data;
-
-@Data
-public class QueryParameter {
-    private String paramName;
-    private String paramValue;
-}

+ 71 - 7
src/main/java/it/bgates/remotebe/service/UserService.java

@@ -12,6 +12,7 @@ import it.bgates.remotebe.exception.UserNotFoundException;
 import it.bgates.remotebe.repository.UserRepository;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.security.Principal;
 import java.util.List;
@@ -97,6 +98,7 @@ public class UserService extends BaseService{
                 });
     }
 
+    @Transactional
     public Boolean disableUser(Integer id, Principal principal) throws UserNotFoundException, PermissionDeniedException {
         BGatesUserDetails userDetails = getUserDetails(principal);
 
@@ -113,18 +115,40 @@ public class UserService extends BaseService{
         }
 
         if (isDistributor()) {
-            // user.getCustomer().get
+            if (!user.getDistributor().getId().equals(userDetails.getDistributor().getId())) {
+                throw new PermissionDeniedException("Permesso negato");
+            }
+        }
+
+        userRepository.setDisabled(user.getId());
+        return true;
+    }
+
+    @Transactional
+    public Boolean enableUser(Integer id, Principal principal) throws UserNotFoundException, PermissionDeniedException {
+        BGatesUserDetails userDetails = getUserDetails(principal);
 
+        User user = userRepository.findById(id).orElse(null);
+        if (user == null) {
+            throw new UserNotFoundException("Utente non trovato");
         }
 
+        // if userDetails is super_admin -> disable user
+        // if userDetails is installer -> disable user if the user is in a company handled by the installer
 
-        if (userDetails.getUsername().equals(user.getUsername())) {
-            user.setEnabled(false);
-            userRepository.save(user);
-            return true;
-        } else {
-            return false;
+        if (isUser()) {
+            throw new PermissionDeniedException("Permesso negato");
         }
+
+        if (isDistributor()) {
+            if (!user.getDistributor().getId().equals(userDetails.getDistributor().getId())) {
+                throw new PermissionDeniedException("Permesso negato");
+            }
+        }
+
+        //user.setEnabled(true);
+        userRepository.setEnabled(user.getId());
+        return true;
     }
 
     public List<User> getUsers(Principal principal) throws UserNotFoundException {
@@ -137,4 +161,44 @@ public class UserService extends BaseService{
         }
         return List.of();
     }
+
+    public Integer getGtCoins(Integer userId) {
+        return userRepository.getGtCoins(userId);
+    }
+
+
+    public Integer subtractGtCoins(User user, Integer coinsNeeded) throws UserNotFoundException {
+        User userToUpdate = userRepository.findById(user.getId()).orElse(null);
+        if (userToUpdate != null) {
+            userToUpdate.setGtCoins(userToUpdate.getGtCoins() - coinsNeeded);
+            userRepository.save(userToUpdate);
+            return userRepository.getGtCoins(userToUpdate.getId());
+        }
+
+        throw new UserNotFoundException("Utente non trovato");
+    }
+
+    @Transactional
+    public Integer addToBalance(Integer amount, Principal principal) throws UserNotFoundException {
+        userDetails = getUserDetails(principal);
+        User user = userRepository.findById(userDetails.getId()).orElse(null);
+
+        if (user != null) {
+
+            userRepository.addToGtCoins(user.getId(), amount);
+            return userRepository.getGtCoins(user.getId());
+        }
+
+        throw new UserNotFoundException("Utente non trovato");
+    }
+
+    public Integer addGtCoins(User user, Integer coinsToAdd) throws UserNotFoundException {
+        User userToUpdate = userRepository.findById(user.getId()).orElse(null);
+        if (userToUpdate != null) {
+            userRepository.detractToGtCoins(user.getId(), coinsToAdd);
+            return userRepository.getGtCoins(user.getId());
+        }
+        throw new UserNotFoundException("Utente non trovato");
+    }
+
 }

+ 105 - 0
src/main/java/it/bgates/remotebe/service/skebby/SkebbyService.java

@@ -0,0 +1,105 @@
+package it.bgates.remotebe.service.skebby;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpVersion;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpConnectionParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.params.HttpProtocolParamBean;
+import org.apache.http.util.EntityUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestClient;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+@Service
+public class SkebbyService {
+
+    private final RestClient restClient;
+
+    @Value("${skebby.user-key}")
+    private String userKey;
+
+    @Value("${skebby.session-key}")
+    private String sessionKey;
+
+    public SkebbyService(RestClient.Builder restClientBuilder) {
+        this.restClient = restClientBuilder
+                .baseUrl("https://api.skebby.it/API/v1.0/REST")
+                .build();
+    }
+
+
+
+    /**
+     * Sends a simple SMS to one recipient.
+     */
+    public String sendSms(String recipient, String message) {
+        try {
+            return skebbyGatewaySendSMS(recipient, message, "send_sms_classic", null, null);
+        } catch (Exception ex) {
+            System.out.println("Error sending SMS: " + ex.getMessage());
+            return null;
+        }
+    }
+
+
+
+    public  String skebbyGatewaySendSMS(String  recipient, String text, String smsType, String senderNumber, String senderString) throws IOException{
+        List<String> recipients = List.of(recipient);
+        return skebbyGatewaySendSMS(recipients, text, smsType,  senderNumber,  senderString, "UTF-8");
+    }
+
+    protected  String skebbyGatewaySendSMS(List<String> recipients, String text, String smsType, String senderNumber, String senderString, String charset) throws IOException {
+
+        if (!charset.equals("UTF-8") && !charset.equals("ISO-8859-1")) {
+
+            throw new IllegalArgumentException("Charset not supported.");
+        }
+
+        String endpoint = "http://gateway.skebby.it/api/send/smseasy/advanced/http.php";
+        HttpParams params = new BasicHttpParams();
+        HttpConnectionParams.setConnectionTimeout(params, 10*1000);
+        DefaultHttpClient httpclient = new DefaultHttpClient(params);
+        HttpProtocolParamBean paramsBean = new HttpProtocolParamBean(params);
+        paramsBean.setVersion(HttpVersion.HTTP_1_1);
+        paramsBean.setContentCharset(charset);
+        paramsBean.setHttpElementCharset(charset);
+
+        List<NameValuePair> formparams = new ArrayList<NameValuePair>();
+        formparams.add(new BasicNameValuePair("method", smsType));
+        formparams.add(new BasicNameValuePair("username", userKey));
+        formparams.add(new BasicNameValuePair("password", sessionKey));
+        if(null != senderNumber)
+            formparams.add(new BasicNameValuePair("sender_number", senderNumber));
+        if(null != senderString)
+            formparams.add(new BasicNameValuePair("sender_string", senderString));
+
+        for (String recipient : recipients) {
+            formparams.add(new BasicNameValuePair("recipients[]", recipient));
+        }
+        formparams.add(new BasicNameValuePair("text", text));
+        formparams.add(new BasicNameValuePair("charset", charset));
+
+
+        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, charset);
+        HttpPost post = new HttpPost(endpoint);
+        post.setEntity(entity);
+
+        HttpResponse response = httpclient.execute(post);
+        HttpEntity resultEntity = response.getEntity();
+        if(null != resultEntity){
+            return EntityUtils.toString(resultEntity);
+        }
+        return null;
+    }
+}

+ 10 - 2
src/main/resources/application-dev.properties

@@ -1,18 +1,25 @@
-
 server.port = 9100
 
+spring.output.ansi.enabled=never
+
 #DB connection
 spring.datasource.url=jdbc:mariadb://127.0.0.1:3306/remote?autoReconnect=true&zeroDateTimeBehavior=convertToNull&serverTimezone=Europe/Rome
 spring.datasource.username=root
 spring.datasource.password=kranio-10
 
+#spring.datasource.url=jdbc:mariadb://192.168.0.94:3306/remote?autoReconnect=true&zeroDateTimeBehavior=convertToNull&serverTimezone=Europe/Rome
+#spring.datasource.username=remote
+#spring.datasource.password=manager
+
+application.security.jwt.expiration=60000000
+
 spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
 # spring.jpa.database-platform=org.hibernate.dialect.MariaDBDialect
 spring.jpa.show-sql=true
 
 spring.jpa.hibernate.ddl-auto=update
 #update
-spring.session.jdbc.initialize-schema=always
+spring.session.jdbc.initialize-schema=never
 
 #database pooling
 spring.datasource.hikari.maximum-pool-size=50
@@ -22,4 +29,5 @@ server.error.include-message=always
 server.servlet.session.timeout=600s
 server.error.include-stacktrace=always
 
+# debug =true
 

+ 5 - 1
src/main/resources/application-prod.properties

@@ -2,6 +2,8 @@ spring.application.name=remote-be
 
 server.port = 9100
 
+spring.output.ansi.enabled=never
+
 spring.datasource.url=jdbc:mariadb://127.0.0.1:3306/remote?autoReconnect=true&zeroDateTimeBehavior=convertToNull&serverTimezone=Europe/Rome
 spring.datasource.username=remote
 spring.datasource.password=manager
@@ -20,7 +22,9 @@ spring.session.jdbc.initialize-schema=always
 spring.datasource.hikari.maximum-pool-size=50
 
 # detailed error reporting
-# server.error.include-stacktrace=never
+# server.error.include-stacktrace=NEVER
 server.error.include-stacktrace=always
 server.error.include-message=always
 server.servlet.session.timeout=600s
+
+# debug = false

+ 7 - 1
src/main/resources/application.properties

@@ -2,9 +2,10 @@ spring.profiles.active=${SPRING_PROFILES_ACTIVE:dev}
 
 spring.application.name=remote-be
 
+spring.output.ansi.enabled=never
 
 application.security.jwt.secret-key=404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970
-application.security.jwt.expiration=600000
+application.security.jwt.expiration=60000000
 application.security.jwt.refresh-token.expiration=604800000
 
 spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
@@ -13,4 +14,9 @@ spring.jpa.database-platform=org.hibernate.dialect.MariaDBDialect
 # swagger-ui custom path
 springdoc.swagger-ui.path=/v3/swagger-ui.html
 
+# avoid serialilzation errors on empty beans
+spring.jackson.serialization.fail-on-empty-beans=false
 
+# skebby credentials
+skebby.user-key=massimo.bertoli@bgates.it
+skebby.session-key=609059ae-1cab-4ac1-89e0-ed8056e73022