Button dimmer now with new rest conversation scheme
This commit is contained in:
parent
7bb05b705f
commit
3a97d40858
14 changed files with 293 additions and 98 deletions
|
@ -23,6 +23,7 @@ public class GsonConfig {
|
|||
private Gson gson() {
|
||||
final GsonBuilder builder = new GsonBuilder();
|
||||
builder.registerTypeAdapter(Json.class, new SpringfoxJsonToGsonAdapter());
|
||||
builder.addSerializationExclusionStrategy(new AnnotationExclusionStrategy());
|
||||
return builder.create();
|
||||
}
|
||||
}
|
||||
|
@ -34,3 +35,16 @@ class SpringfoxJsonToGsonAdapter implements JsonSerializer<Json> {
|
|||
return JsonParser.parseString(json.value());
|
||||
}
|
||||
}
|
||||
|
||||
/** GSON exclusion strategy to exclude attributes with @GsonExclude */
|
||||
class AnnotationExclusionStrategy implements ExclusionStrategy {
|
||||
@Override
|
||||
public boolean shouldSkipField(FieldAttributes f) {
|
||||
return f.getAnnotation(GsonExclude.class) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldSkipClass(Class<?> clazz) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.config;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface GsonExclude {}
|
|
@ -2,11 +2,12 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
|
|||
|
||||
import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList;
|
||||
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.ButtonDimmerDimRequest;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.ButtonDimmerSaveRequest;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ButtonDimmer;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ButtonDimmerRepository;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
|
@ -15,37 +16,74 @@ import org.springframework.web.bind.annotation.*;
|
|||
@RestController
|
||||
@EnableAutoConfiguration
|
||||
@RequestMapping("/buttonDimmer")
|
||||
public class ButtonDimmerController {
|
||||
@Autowired private ButtonDimmerRepository buttonDimmerService;
|
||||
public class ButtonDimmerController
|
||||
extends InputDeviceConnectionController<ButtonDimmer, DimmableLight> {
|
||||
private ButtonDimmerRepository buttonDimmerRepository;
|
||||
private DimmableLightRepository dimmableLightRepository;
|
||||
|
||||
@Autowired
|
||||
protected ButtonDimmerController(
|
||||
ButtonDimmerRepository inputRepository, DimmableLightRepository outputRepository) {
|
||||
super(inputRepository, outputRepository);
|
||||
this.buttonDimmerRepository = inputRepository;
|
||||
this.dimmableLightRepository = outputRepository;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public List<ButtonDimmer> findAll() {
|
||||
return toList(buttonDimmerService.findAll());
|
||||
return toList(buttonDimmerRepository.findAll());
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ButtonDimmer findById(@PathVariable("id") long id) throws NotFoundException {
|
||||
return buttonDimmerService.findById(id).orElseThrow(NotFoundException::new);
|
||||
return buttonDimmerRepository.findById(id).orElseThrow(NotFoundException::new);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ButtonDimmer create(@Valid @RequestBody final ButtonDimmerSaveRequest bd) {
|
||||
ButtonDimmer newBD = new ButtonDimmer();
|
||||
newBD.setLights(bd.getLights());
|
||||
newBD.setId(bd.getId());
|
||||
newBD.setName(bd.getName());
|
||||
newBD.setRoomId(bd.getRoomId());
|
||||
|
||||
return buttonDimmerService.save(newBD);
|
||||
return buttonDimmerRepository.save(newBD);
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
public ButtonDimmer update(@Valid @RequestBody ButtonDimmerSaveRequest bd) {
|
||||
return this.create(bd);
|
||||
@PutMapping("/dim")
|
||||
public Set<DimmableLight> dim(@Valid @RequestBody final ButtonDimmerDimRequest bd)
|
||||
throws NotFoundException {
|
||||
final ButtonDimmer buttonDimmer =
|
||||
buttonDimmerRepository.findById(bd.getId()).orElseThrow(NotFoundException::new);
|
||||
|
||||
switch (bd.getDimType()) {
|
||||
case UP:
|
||||
buttonDimmer.increaseIntensity();
|
||||
break;
|
||||
case DOWN:
|
||||
buttonDimmer.decreaseIntensity();
|
||||
break;
|
||||
}
|
||||
|
||||
dimmableLightRepository.saveAll(buttonDimmer.getLights());
|
||||
|
||||
return buttonDimmer.getOutputs();
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/lights")
|
||||
public Set<DimmableLight> addLight(
|
||||
@PathVariable("id") long inputId, @RequestParam("lightId") Long lightId)
|
||||
throws NotFoundException {
|
||||
return addOutput(inputId, lightId);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}/lights")
|
||||
public Set<DimmableLight> removeLight(
|
||||
@PathVariable("id") long inputId, @RequestParam("lightId") Long lightId)
|
||||
throws NotFoundException {
|
||||
return removeOutput(inputId, lightId);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public void delete(@PathVariable("id") long id) {
|
||||
buttonDimmerService.deleteById(id);
|
||||
buttonDimmerRepository.deleteById(id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,19 +33,12 @@ public class DimmableLightController {
|
|||
public DimmableLight create(@Valid @RequestBody DimmableLightSaveRequest dl) {
|
||||
DimmableLight newDL = new DimmableLight();
|
||||
newDL.setIntensity(dl.getIntensity());
|
||||
newDL.setId(dl.getId());
|
||||
newDL.setName(dl.getName());
|
||||
newDL.setRoomId(dl.getRoomId());
|
||||
|
||||
return dimmableLightService.save(newDL);
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
public DimmableLight update(@Valid @RequestBody DimmableLightSaveRequest dl) {
|
||||
dl.setId(0);
|
||||
return this.create(dl);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public void delete(@PathVariable("id") long id) {
|
||||
dimmableLightService.deleteById(id);
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
|
||||
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An abstract controller for an input device that has output connected to it. Aids to create the
|
||||
* output add and output remove route
|
||||
*
|
||||
* @param <I> the type of device this controller is for
|
||||
* @param <O> the output device attached to I
|
||||
*/
|
||||
public abstract class InputDeviceConnectionController<
|
||||
I extends InputDevice & OutputConnectable<O>,
|
||||
O extends OutputDevice & InputConnectable<I>> {
|
||||
|
||||
private class IOPair {
|
||||
private final I input;
|
||||
private final O output;
|
||||
|
||||
private IOPair(I input, O output) {
|
||||
this.input = input;
|
||||
this.output = output;
|
||||
}
|
||||
}
|
||||
|
||||
private DeviceRepository<I> inputRepository;
|
||||
|
||||
private DeviceRepository<O> outputReposiory;
|
||||
|
||||
protected InputDeviceConnectionController(
|
||||
DeviceRepository<I> inputRepository, DeviceRepository<O> outputRepository) {
|
||||
this.inputRepository = inputRepository;
|
||||
this.outputReposiory = outputRepository;
|
||||
}
|
||||
|
||||
private IOPair checkConnectionIDs(Long inputId, Long outputId) throws NotFoundException {
|
||||
final I input =
|
||||
inputRepository
|
||||
.findById(inputId)
|
||||
.orElseThrow(() -> new NotFoundException("input device"));
|
||||
final O output =
|
||||
outputReposiory
|
||||
.findById(outputId)
|
||||
.orElseThrow(() -> new NotFoundException("output device"));
|
||||
return new IOPair(input, output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the output device connection creation (add) route
|
||||
*
|
||||
* @param inputId input device id
|
||||
* @param outputId output device id
|
||||
* @return the list of output devices attached to the input device of id inputId
|
||||
* @throws NotFoundException if inputId or outputId are not valid
|
||||
*/
|
||||
protected Set<O> addOutput(Long inputId, Long outputId) throws NotFoundException {
|
||||
final IOPair pair = checkConnectionIDs(inputId, outputId);
|
||||
pair.input.getOutputs().add(pair.output);
|
||||
pair.output.connect(pair.input.getId());
|
||||
outputReposiory.save(pair.output);
|
||||
return pair.input.getOutputs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the output device connection destruction (remove) route
|
||||
*
|
||||
* @param inputId input device id
|
||||
* @param outputId output device id
|
||||
* @return the list of output devices attached to the input device of id inputId
|
||||
* @throws NotFoundException if inputId or outputId are not valid
|
||||
*/
|
||||
protected Set<O> removeOutput(Long inputId, Long outputId) throws NotFoundException {
|
||||
final IOPair pair = checkConnectionIDs(inputId, outputId);
|
||||
pair.input.getOutputs().remove(pair.output);
|
||||
pair.output.connect(null);
|
||||
outputReposiory.save(pair.output);
|
||||
return pair.input.getOutputs();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/** A 'dim' event from a button dimmer. */
|
||||
public class ButtonDimmerDimRequest {
|
||||
|
||||
/** The device id */
|
||||
@NotNull private Long id;
|
||||
|
||||
public enum DimType {
|
||||
UP,
|
||||
DOWN;
|
||||
}
|
||||
|
||||
/** Whether the dim is up or down */
|
||||
@NotNull private DimType dimType;
|
||||
|
||||
public DimType getDimType() {
|
||||
return dimType;
|
||||
}
|
||||
|
||||
public void setDimType(DimType dimType) {
|
||||
this.dimType = dimType;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
|
@ -1,21 +1,8 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
|
||||
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DimmableLight;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Room;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import javax.persistence.*;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
public class ButtonDimmerSaveRequest {
|
||||
@Lob private Set<DimmableLight> lights = new HashSet<DimmableLight>();
|
||||
|
||||
/** Device identifier */
|
||||
private long id;
|
||||
|
||||
/** The room this device belongs in */
|
||||
private Room room;
|
||||
|
||||
/**
|
||||
* The room this device belongs in, as a foreign key id. To use when updating and inserting from
|
||||
* a REST call.
|
||||
|
@ -25,18 +12,6 @@ public class ButtonDimmerSaveRequest {
|
|||
/** The name of the device as assigned by the user (e.g. 'Master bedroom light') */
|
||||
@NotNull private String name;
|
||||
|
||||
public Set<DimmableLight> getLights() {
|
||||
return this.lights;
|
||||
}
|
||||
|
||||
public void setLights(Set<DimmableLight> newLights) {
|
||||
this.lights = newLights;
|
||||
}
|
||||
|
||||
public void setRoom(Room room) {
|
||||
this.room = room;
|
||||
}
|
||||
|
||||
public void setRoomId(Long roomId) {
|
||||
this.roomId = roomId;
|
||||
}
|
||||
|
@ -45,14 +20,6 @@ public class ButtonDimmerSaveRequest {
|
|||
this.name = name;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Room getRoom() {
|
||||
return room;
|
||||
}
|
||||
|
||||
public Long getRoomId() {
|
||||
return roomId;
|
||||
}
|
||||
|
@ -60,8 +27,4 @@ public class ButtonDimmerSaveRequest {
|
|||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
|
||||
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Room;
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
@ -13,12 +12,6 @@ public class DimmableLightSaveRequest {
|
|||
@Max(100)
|
||||
private Integer intensity = 0;
|
||||
|
||||
/** Device identifier */
|
||||
private long id;
|
||||
|
||||
/** The room this device belongs in */
|
||||
private Room room;
|
||||
|
||||
/**
|
||||
* The room this device belongs in, as a foreign key id. To use when updating and inserting from
|
||||
* a REST call.
|
||||
|
@ -28,10 +21,6 @@ public class DimmableLightSaveRequest {
|
|||
/** The name of the device as assigned by the user (e.g. 'Master bedroom light') */
|
||||
@NotNull private String name;
|
||||
|
||||
public void setRoom(Room room) {
|
||||
this.room = room;
|
||||
}
|
||||
|
||||
public void setRoomId(Long roomId) {
|
||||
this.roomId = roomId;
|
||||
}
|
||||
|
@ -40,14 +29,6 @@ public class DimmableLightSaveRequest {
|
|||
this.name = name;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Room getRoom() {
|
||||
return room;
|
||||
}
|
||||
|
||||
public Long getRoomId() {
|
||||
return roomId;
|
||||
}
|
||||
|
@ -60,14 +41,7 @@ public class DimmableLightSaveRequest {
|
|||
return intensity;
|
||||
}
|
||||
|
||||
public void setIntensity(Integer intensity) throws IllegalArgumentException {
|
||||
if (intensity < 0 || intensity > 100) {
|
||||
throw new IllegalArgumentException("The intensity level can't go below 0 or above 100");
|
||||
}
|
||||
public void setIntensity(Integer intensity) {
|
||||
this.intensity = intensity;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,10 @@ import org.springframework.web.bind.annotation.ResponseStatus;
|
|||
@ResponseStatus(code = HttpStatus.NOT_FOUND)
|
||||
public class NotFoundException extends Exception {
|
||||
public NotFoundException() {
|
||||
super("Not Found");
|
||||
super("Not found");
|
||||
}
|
||||
|
||||
public NotFoundException(String what) {
|
||||
super(what + " not found");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
|
||||
|
||||
|
||||
import java.util.Set;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.OneToMany;
|
||||
|
@ -10,7 +9,10 @@ import javax.persistence.OneToMany;
|
|||
* dimmer with a '+' and a '-' button)
|
||||
*/
|
||||
@Entity
|
||||
public class ButtonDimmer extends Dimmer {
|
||||
public class ButtonDimmer extends Dimmer implements OutputConnectable<DimmableLight> {
|
||||
/** The delta amount to apply to a increase or decrease intensity */
|
||||
public static final int DIM_INCREMENT = 10;
|
||||
|
||||
public ButtonDimmer() {
|
||||
super("button-dimmer");
|
||||
}
|
||||
|
@ -18,17 +20,19 @@ public class ButtonDimmer extends Dimmer {
|
|||
@OneToMany(mappedBy = "dimmer")
|
||||
private Set<DimmableLight> lights;
|
||||
|
||||
/** Increases the current intensity level of the dimmable light by 1 */
|
||||
/** Increases the current intensity level of the dimmable light by DIM_INCREMENT */
|
||||
public void increaseIntensity() {
|
||||
for (DimmableLight dl : lights) {
|
||||
dl.setIntensity(dl.getIntensity() + 1);
|
||||
dl.setIntensity(dl.getIntensity() + DIM_INCREMENT);
|
||||
System.out.println("malusa: " + dl.getIntensity());
|
||||
}
|
||||
}
|
||||
|
||||
/** Decreases the current intensity level of the dimmable light by 1 */
|
||||
/** Decreases the current intensity level of the dimmable light by DIM_INCREMENT */
|
||||
public void decreaseIntensity() {
|
||||
for (DimmableLight dl : lights) {
|
||||
dl.setIntensity(dl.getIntensity() - 1);
|
||||
dl.setIntensity(dl.getIntensity() - DIM_INCREMENT);
|
||||
System.out.println("malusa: " + dl.getIntensity());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,6 +68,11 @@ public class ButtonDimmer extends Dimmer {
|
|||
return this.lights;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<DimmableLight> getOutputs() {
|
||||
return this.lights;
|
||||
}
|
||||
|
||||
public void setLights(Set<DimmableLight> newLights) {
|
||||
this.lights = newLights;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
|
||||
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.Min;
|
||||
|
@ -9,18 +11,24 @@ import javax.validation.constraints.NotNull;
|
|||
|
||||
/** Represent a dimmable light */
|
||||
@Entity
|
||||
public class DimmableLight extends Light {
|
||||
public class DimmableLight extends Light implements InputConnectable<ButtonDimmer> {
|
||||
|
||||
public DimmableLight() {
|
||||
super("light");
|
||||
}
|
||||
|
||||
@ManyToOne private Dimmer dimmer;
|
||||
@ManyToOne
|
||||
@GsonExclude
|
||||
@JoinColumn(name = "dimmer_id", updatable = false, insertable = false)
|
||||
private Dimmer dimmer;
|
||||
|
||||
@Column(name = "dimmer_id")
|
||||
private Long dimmerId;
|
||||
|
||||
/** The light intensity value. Goes from 0 (off) to 100 (on) */
|
||||
@NotNull
|
||||
@Column(nullable = false)
|
||||
@Min(1)
|
||||
@Min(0)
|
||||
@Max(100)
|
||||
private Integer intensity = 0;
|
||||
|
||||
|
@ -28,10 +36,39 @@ public class DimmableLight extends Light {
|
|||
return intensity;
|
||||
}
|
||||
|
||||
public void setIntensity(Integer intensity) throws IllegalArgumentException {
|
||||
if (intensity < 0 || intensity > 100) {
|
||||
throw new IllegalArgumentException("The intensity level can't go below 0 or above 100");
|
||||
/**
|
||||
* Sets the intensity to a certain level. Out of bound values are corrected to the respective
|
||||
* extremums. An intensity level of 0 turns the light off, but keeps the old intensity level
|
||||
* stored.
|
||||
*
|
||||
* @param intensity the intensity level (may be out of bounds)
|
||||
*/
|
||||
public void setIntensity(Integer intensity) {
|
||||
if (intensity <= 0) {
|
||||
this.intensity = 0;
|
||||
setOn(false);
|
||||
} else {
|
||||
setOn(true);
|
||||
if (intensity > 100) {
|
||||
this.intensity = 100;
|
||||
} else {
|
||||
this.intensity = intensity;
|
||||
}
|
||||
}
|
||||
this.intensity = intensity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOn(boolean on) {
|
||||
super.setOn(on);
|
||||
if (on) {
|
||||
intensity = 100;
|
||||
} else {
|
||||
intensity = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(Long inputId) {
|
||||
this.dimmerId = inputId;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
|
||||
|
||||
|
||||
/**
|
||||
* An output device to which an input can be connected
|
||||
*
|
||||
* @param <O> the type of input device that can be connected to this device
|
||||
*/
|
||||
public interface InputConnectable<O extends InputDevice> {
|
||||
|
||||
/**
|
||||
* Connect an input device to this output device. This method should set the corresponding FK in
|
||||
* the entity.
|
||||
*
|
||||
* @param inputId the input device id, null for deleting the connection
|
||||
*/
|
||||
void connect(Long inputId);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An input device to which outputs can be connected
|
||||
*
|
||||
* @param <O> the type of output device that can be connected to this device
|
||||
*/
|
||||
public interface OutputConnectable<O extends OutputDevice> {
|
||||
|
||||
/**
|
||||
* Get the set of all output devices connected to this device
|
||||
*
|
||||
* @return The set of outputs connected to this device
|
||||
*/
|
||||
Set<O> getOutputs();
|
||||
}
|
|
@ -42,6 +42,8 @@ public class Sensor extends InputDevice {
|
|||
|
||||
public void setSensor(SensorType sensor) {
|
||||
this.sensor = sensor;
|
||||
|
||||
// TODO: setup hook for sockets live update
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
|
|
Loading…
Reference in a new issue