This commit is contained in:
Patrick Haßel 2024-11-21 15:57:29 +01:00
parent fdf64ebd17
commit b14c8d63d2
24 changed files with 664 additions and 11 deletions

View File

@ -0,0 +1,30 @@
import {Property} from "../Property/Property";
import {orNull, validateString} from "../common/validators";
export class Shutter {
constructor(
readonly uuid: string,
readonly name: string,
readonly slug: string,
readonly positionPropertyId: string,
readonly positionProperty: Property | null,
) {
// -
}
static fromJson(json: any): Shutter {
return new Shutter(
validateString(json.uuid),
validateString(json.name),
validateString(json.slug),
validateString(json.positionPropertyId),
orNull(json.positionProperty, Property.fromJson),
);
}
static trackBy(index: number, shutter: Shutter) {
return shutter.uuid;
}
}

View File

@ -0,0 +1,5 @@
export class ShutterFilter {
search: string = "";
}

View File

@ -0,0 +1,32 @@
import {Injectable} from '@angular/core';
import {CrudService} from '../common/CrudService';
import {Shutter} from './Shutter';
import {ApiService} from '../common/api.service';
import {Next} from '../common/types';
import {ShutterFilter} from './ShutterFilter';
@Injectable({
providedIn: 'root'
})
export class ShutterService extends CrudService<Shutter> {
constructor(
api: ApiService,
) {
super(api, ['Shutter'], Shutter.fromJson);
}
getByUuid(uuid: string, next: Next<Shutter>): void {
this.getSingle(['getByUuid', uuid], next);
}
list(filter: ShutterFilter | null, next: Next<Shutter[]>): void {
this.postList(['list'], filter, next);
}
setPosition(shutter: Shutter, position: number, next?: Next<void>): void {
this.getNone(['setPosition', shutter.uuid, position], next);
}
}

View File

@ -1,6 +1,7 @@
<div class="flexBox"> <div class="flexBox">
<div class="flexBoxFixed menu"> <div class="flexBoxFixed menu">
<div class="item itemLeft" routerLink="DeviceList" routerLinkActive="active">Geräte</div> <div class="item itemLeft" routerLink="DeviceList" routerLinkActive="active">Geräte</div>
<div class="item itemLeft" routerLink="ShutterList" routerLinkActive="active">Rollläden</div>
<div class="item itemRight" routerLink="GroupList" routerLinkActive="active">KNX</div> <div class="item itemRight" routerLink="GroupList" routerLinkActive="active">KNX</div>
</div> </div>
<div class="flexBoxRest"> <div class="flexBoxRest">

View File

@ -1,9 +1,11 @@
import {Routes} from '@angular/router'; import {Routes} from '@angular/router';
import {KnxGroupListPageComponent} from './pages/knx-group-list-page/knx-group-list-page.component'; import {KnxGroupListPageComponent} from './pages/knx-group-list-page/knx-group-list-page.component';
import {DeviceListPageComponent} from './pages/device-list-page/device-list-page.component'; import {DeviceListPageComponent} from './pages/device-list-page/device-list-page.component';
import {ShutterListPageComponent} from './pages/shutter-list-page/shutter-list-page.component';
export const routes: Routes = [ export const routes: Routes = [
{path: 'DeviceList', component: DeviceListPageComponent}, {path: 'DeviceList', component: DeviceListPageComponent},
{path: 'ShutterList', component: ShutterListPageComponent},
{path: 'GroupList', component: KnxGroupListPageComponent}, {path: 'GroupList', component: KnxGroupListPageComponent},
{path: '**', redirectTo: 'GroupList'}, {path: '**', redirectTo: 'GroupList'},
]; ];

View File

@ -0,0 +1,8 @@
<div class="flexBox">
<div class="flexBoxFixed">
<input [(ngModel)]="filter.search" (ngModelChange)="fetchDelayed()" placeholder="Suchen...">
</div>
<div class="flexBoxRest">
<app-shutter-list [shutterList]="shutterList"></app-shutter-list>
</div>
</div>

View File

@ -0,0 +1,5 @@
@import "../../../config";
input {
border-bottom: @border solid lightgray;
}

View File

@ -0,0 +1,70 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
import {ShutterListComponent} from '../../shared/shutter-list/shutter-list.component';
import {Shutter} from '../../api/Shutter/Shutter';
import {ShutterService} from '../../api/Shutter/shutter.service';
import {FormsModule} from '@angular/forms';
import {ShutterFilter} from '../../api/Shutter/ShutterFilter';
import {Subscription} from 'rxjs';
import {KnxGroupListComponent} from '../../shared/knx-group-list/knx-group-list.component';
import {ApiService} from '../../api/common/api.service';
@Component({
selector: 'app-shutter-list-page',
standalone: true,
imports: [
ShutterListComponent,
FormsModule,
KnxGroupListComponent
],
templateUrl: './shutter-list-page.component.html',
styleUrl: './shutter-list-page.component.less'
})
export class ShutterListPageComponent implements OnInit, OnDestroy {
private readonly subs: Subscription[] = [];
protected shutterList: Shutter[] = [];
protected filter: ShutterFilter = new ShutterFilter();
private fetchTimeout: any;
constructor(
protected readonly shutterService: ShutterService,
protected readonly apiService: ApiService,
) {
// -
}
ngOnInit(): void {
this.fetch();
this.subs.push(this.shutterService.subscribe(shutter => this.updateShutter(shutter)));
this.apiService.connected(() => this.fetch());
}
ngOnDestroy(): void {
this.subs.forEach(sub => sub.unsubscribe());
}
fetchDelayed() {
if (this.fetchTimeout) {
clearTimeout(this.fetchTimeout);
this.fetchTimeout = undefined;
}
this.fetchTimeout = setTimeout(() => this.fetch(), 300)
}
private fetch() {
this.shutterService.list(this.filter, list => this.shutterList = list)
}
private updateShutter(shutter: Shutter) {
const index = this.shutterList.findIndex(d => d.uuid === shutter.uuid);
if (index >= 0) {
this.shutterList.splice(index, 1, shutter);
} else {
this.fetch();
}
}
}

View File

@ -0,0 +1,5 @@
<div class="window" (click)="activate.emit(position)">
<div class="shutter" [style.height]="position + '%'">
<!-- -->
</div>
</div>

View File

@ -0,0 +1,14 @@
@import "../../../../config";
.window {
width: 100%;
height: 100%;
background-color: lightskyblue;
border: @border solid black;
.shutter {
background-color: saddlebrown;
border-bottom: @border solid black;
}
}

View File

@ -0,0 +1,18 @@
import {Component, EventEmitter, Input, Output} from '@angular/core';
@Component({
selector: 'app-shutter-icon',
standalone: true,
imports: [],
templateUrl: './shutter-icon.component.html',
styleUrl: './shutter-icon.component.less'
})
export class ShutterIconComponent {
@Input()
position?: number
@Output()
activate: EventEmitter<number> = new EventEmitter();
}

View File

@ -0,0 +1,41 @@
<div class="shutterList tileContainer">
<div class="tile" *ngFor="let shutter of shutterList; trackBy: Shutter.trackBy">
<div class="shutter tileInner">
<div class="name">
{{ shutter.name }}
</div>
<div class="icon">
<app-shutter-icon [position]="shutter.positionProperty?.state?.value"></app-shutter-icon>
</div>
<div class="timestamp details">
{{ shutter.positionProperty?.lastValueChange | relative:now }}
</div>
<div class="actions">
<div class="action">
<app-shutter-icon [position]="0" (activate)="shutterService.setPosition(shutter, $event)"></app-shutter-icon>
</div>
<div class="action">
<app-shutter-icon [position]="50" (activate)="shutterService.setPosition(shutter, $event)"></app-shutter-icon>
</div>
<div class="action">
<app-shutter-icon [position]="80" (activate)="shutterService.setPosition(shutter, $event)"></app-shutter-icon>
</div>
<div class="action">
<app-shutter-icon [position]="90" (activate)="shutterService.setPosition(shutter, $event)"></app-shutter-icon>
</div>
<div class="action">
<app-shutter-icon [position]="100" (activate)="shutterService.setPosition(shutter, $event)"></app-shutter-icon>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,40 @@
@import "../../../config";
.shutterList {
overflow-y: auto;
height: 100%;
.shutter {
.name {
float: left;
}
.icon {
clear: left;
float: left;
width: 4em;
aspect-ratio: 1;
}
.timestamp {
float: right;
font-size: 80%;
}
.actions {
clear: right;
float: right;
div {
float: left;
margin-left: @space;
width: 3em;
aspect-ratio: 1;
}
}
}
}

View File

@ -0,0 +1,47 @@
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
import {NgClass, NgForOf} from '@angular/common';
import {Shutter} from '../../api/Shutter/Shutter';
import {ShutterService} from '../../api/Shutter/shutter.service';
import {RelativePipe} from '../../api/common/relative.pipe';
import {Subscription, timer} from 'rxjs';
import {ShutterIconComponent} from './shutter-icon/shutter-icon.component';
@Component({
selector: 'app-shutter-list',
standalone: true,
imports: [
NgForOf,
NgClass,
RelativePipe,
ShutterIconComponent
],
templateUrl: './shutter-list.component.html',
styleUrl: './shutter-list.component.less'
})
export class ShutterListComponent implements OnInit, OnDestroy {
protected readonly Shutter = Shutter;
private readonly subs: Subscription[] = [];
protected now: Date = new Date();
@Input()
shutterList: Shutter[] = [];
constructor(
protected readonly shutterService: ShutterService,
) {
// -
}
ngOnInit(): void {
this.now = new Date();
this.subs.push(timer(1000, 1000).subscribe(() => this.now = new Date()));
}
ngOnDestroy(): void {
this.subs.forEach(sub => sub.unsubscribe());
}
}

View File

@ -3,6 +3,7 @@ package de.ph87.home.demo;
import de.ph87.home.device.DeviceService; import de.ph87.home.device.DeviceService;
import de.ph87.home.knx.property.KnxPropertyService; import de.ph87.home.knx.property.KnxPropertyService;
import de.ph87.home.knx.property.KnxPropertyType; import de.ph87.home.knx.property.KnxPropertyType;
import de.ph87.home.shutter.ShutterService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.boot.context.event.ApplicationStartedEvent;
@ -21,21 +22,42 @@ public class DemoService {
private final DeviceService deviceService; private final DeviceService deviceService;
private final ShutterService shutterService;
@EventListener(ApplicationStartedEvent.class) @EventListener(ApplicationStartedEvent.class)
public void startup() { public void startup() {
knxPropertyService.create("eg_ambiente", KnxPropertyType.BOOLEAN, adr(849), adr(848)); knxPropertyService.create("eg_ambiente", KnxPropertyType.BOOLEAN, adr(849), adr(848));
knxPropertyService.create("fernseher", KnxPropertyType.BOOLEAN, adr(20), adr(4));
knxPropertyService.create("verstaerker", KnxPropertyType.BOOLEAN, adr(825), adr(824));
knxPropertyService.create("fensterdeko", KnxPropertyType.BOOLEAN, adr(1823), adr(1822));
knxPropertyService.create("haengelampe", KnxPropertyType.BOOLEAN, adr(1794), adr(1799));
knxPropertyService.create("receiver", KnxPropertyType.BOOLEAN, adr(2561), adr(2560));
deviceService.create("EG Ambiente", "eg_ambiente", "eg_ambiente"); deviceService.create("EG Ambiente", "eg_ambiente", "eg_ambiente");
knxPropertyService.create("fernseher", KnxPropertyType.BOOLEAN, adr(20), adr(4));
deviceService.create("Wohnzimmer Fernseher", "fernseher", "fernseher"); deviceService.create("Wohnzimmer Fernseher", "fernseher", "fernseher");
knxPropertyService.create("verstaerker", KnxPropertyType.BOOLEAN, adr(825), adr(824));
deviceService.create("Wohnzimmer Verstärker", "verstaerker", "verstaerker"); deviceService.create("Wohnzimmer Verstärker", "verstaerker", "verstaerker");
knxPropertyService.create("fensterdeko", KnxPropertyType.BOOLEAN, adr(1823), adr(1822));
deviceService.create("Wohnzimmer Fenster", "fensterdeko", "fensterdeko"); deviceService.create("Wohnzimmer Fenster", "fensterdeko", "fensterdeko");
knxPropertyService.create("haengelampe", KnxPropertyType.BOOLEAN, adr(1794), adr(1799));
deviceService.create("Wohnzimmer Hängelampe", "haengelampe", "haengelampe"); deviceService.create("Wohnzimmer Hängelampe", "haengelampe", "haengelampe");
knxPropertyService.create("receiver", KnxPropertyType.BOOLEAN, adr(2561), adr(2560));
deviceService.create("Receiver", "receiver", "receiver"); deviceService.create("Receiver", "receiver", "receiver");
knxPropertyService.create("wohnzimmer_links", KnxPropertyType.DOUBLE, adr(1048), adr(1048));
shutterService.create("Wohnzimmer Links", "wohnzimmer_links", "wohnzimmer_links");
knxPropertyService.create("wohnzimmer_rechts", KnxPropertyType.DOUBLE, adr(1811), adr(1811));
shutterService.create("Wohnzimmer Rechts", "wohnzimmer_rechts", "wohnzimmer_rechts");
knxPropertyService.create("kueche_seite", KnxPropertyType.DOUBLE, adr(2316), adr(2316));
shutterService.create("Küche Seite", "kueche_seite", "kueche_seite");
knxPropertyService.create("kueche_theke", KnxPropertyType.DOUBLE, adr(2320), adr(2320));
shutterService.create("Küche Theke", "kueche_theke", "kueche_theke");
knxPropertyService.create("kueche_tuer", KnxPropertyType.DOUBLE, adr(2324), adr(2324));
shutterService.create("Küche Tür", "kueche_tuer", "kueche_tuer");
} }
private static GroupAddress adr(final int rawGroupAddress) { private static GroupAddress adr(final int rawGroupAddress) {

View File

@ -14,6 +14,7 @@ import java.util.List;
@ToString @ToString
public class DeviceDto implements IWebSocketMessage { public class DeviceDto implements IWebSocketMessage {
@ToString.Exclude
private final List<Object> websocketTopic = List.of("Device"); private final List<Object> websocketTopic = List.of("Device");
@NonNull @NonNull

View File

@ -14,6 +14,7 @@ import java.util.List;
@ToString @ToString
public class GroupDto implements IWebSocketMessage { public class GroupDto implements IWebSocketMessage {
@ToString.Exclude
private final List<Object> websocketTopic = List.of("Knx", "Group"); private final List<Object> websocketTopic = List.of("Knx", "Group");
@NonNull @NonNull

View File

@ -71,11 +71,6 @@ public class KnxPropertyService {
findAllByAddress(event.getDestination()).forEach(knxProperty -> onProcessEvent(knxProperty, event)); findAllByAddress(event.getDestination()).forEach(knxProperty -> onProcessEvent(knxProperty, event));
} }
@NonNull
private List<KnxProperty> findAllByAddress(@NonNull final GroupAddress address) {
return knxPropertyRepository.findDistinctByReadOrWrite(address, address);
}
private void onProcessEvent(@NonNull final KnxProperty knxProperty, @NonNull final ProcessEvent event) { private void onProcessEvent(@NonNull final KnxProperty knxProperty, @NonNull final ProcessEvent event) {
log.debug("onProcessEvent: knxProperty={}, event={}", knxProperty, event); log.debug("onProcessEvent: knxProperty={}, event={}", knxProperty, event);
groupRepository.findByAddress(event.getDestination()).ifPresent(group -> onProcessEvent(knxProperty, event, group)); groupRepository.findByAddress(event.getDestination()).ifPresent(group -> onProcessEvent(knxProperty, event, group));
@ -138,4 +133,9 @@ public class KnxPropertyService {
} }
} }
@NonNull
private List<KnxProperty> findAllByAddress(@NonNull final GroupAddress address) {
return knxPropertyRepository.findDistinctByReadOrWrite(address, address);
}
} }

View File

@ -0,0 +1,39 @@
package de.ph87.home.shutter;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import lombok.*;
import java.util.UUID;
@Entity
@Getter
@ToString
@NoArgsConstructor
public class Shutter {
@Id
@NonNull
private String uuid = UUID.randomUUID().toString();
@NonNull
@Column(nullable = false)
private String name;
@NonNull
@Column(nullable = false, unique = true)
private String slug;
@Setter
@NonNull
@Column(nullable = false)
private String positionPropertyId;
public Shutter(@NonNull final String name, @NonNull final String slug, @NonNull final String positionPropertyId) {
this.name = name;
this.slug = slug;
this.positionPropertyId = positionPropertyId;
}
}

View File

@ -0,0 +1,59 @@
package de.ph87.home.shutter;
import de.ph87.home.property.PropertyNotFound;
import de.ph87.home.property.PropertyNotWritable;
import de.ph87.home.property.PropertyTypeMismatch;
import jakarta.annotation.Nullable;
import jakarta.servlet.http.HttpServletRequest;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import tuwien.auto.calimero.KNXFormatException;
import java.util.List;
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("Shutter")
public class ShutterController {
private final ShutterService shutterService;
@NonNull
@GetMapping("getByUuid/{id}")
@ExceptionHandler(KNXFormatException.class)
private ShutterDto getByUuid(@PathVariable final String id, @NonNull final HttpServletRequest request) {
log.debug("getByUuid: path={}", request.getServletPath());
return shutterService.getByUuidDto(id);
}
@NonNull
@RequestMapping(value = "list", method = {RequestMethod.GET, RequestMethod.POST})
private List<ShutterDto> list(@RequestBody(required = false) @Nullable final ShutterFilter filter, @NonNull final HttpServletRequest request) throws PropertyTypeMismatch {
log.debug("list: path={} filter={}", request.getServletPath(), filter);
return shutterService.list(filter);
}
@NonNull
@GetMapping("get/{uuidOrSlug}")
private ShutterDto get(@PathVariable @NonNull final String uuidOrSlug, @NonNull final HttpServletRequest request) {
log.debug("get: path={}", request.getServletPath());
return shutterService.getByUuidOrSlugDto(uuidOrSlug);
}
@Nullable
@GetMapping("getPosition/{uuidOrSlug}")
private Double getPosition(@PathVariable @NonNull final String uuidOrSlug, @NonNull final HttpServletRequest request) throws PropertyTypeMismatch {
log.debug("getPosition: path={}", request.getServletPath());
return shutterService.getByUuidOrSlugDto(uuidOrSlug).getPositionValue();
}
@GetMapping("setPosition/{uuidOrSlug}/{position}")
private void setPosition(@PathVariable @NonNull final String uuidOrSlug, @PathVariable final double position, @NonNull final HttpServletRequest request) throws PropertyNotFound, PropertyNotWritable, PropertyTypeMismatch {
log.debug("setPosition: path={}", request.getServletPath());
shutterService.setPosition(uuidOrSlug, position);
}
}

View File

@ -0,0 +1,62 @@
package de.ph87.home.shutter;
import de.ph87.home.property.PropertyDto;
import de.ph87.home.property.PropertyTypeMismatch;
import de.ph87.home.web.IWebSocketMessage;
import jakarta.annotation.Nullable;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
import java.util.List;
@Getter
@ToString
public class ShutterDto implements IWebSocketMessage {
@ToString.Exclude
private final List<Object> websocketTopic = List.of("Shutter");
@NonNull
private final String uuid;
@NonNull
private final String name;
@NonNull
private final String slug;
@NonNull
private final String positionPropertyId;
@Nullable
@ToString.Exclude
private final PropertyDto<Double> positionProperty;
public ShutterDto(@NonNull final Shutter shutter, @Nullable final PropertyDto<Double> positionProperty) {
this.uuid = shutter.getUuid();
this.name = shutter.getName();
this.slug = shutter.getSlug();
this.positionPropertyId = shutter.getPositionPropertyId();
this.positionProperty = positionProperty;
}
@Nullable
@ToString.Include
public String position() {
try {
return "" + getPositionValue();
} catch (PropertyTypeMismatch e) {
return "[PropertyTypeMismatch]";
}
}
@Nullable
public Double getPositionValue() throws PropertyTypeMismatch {
if (positionProperty == null) {
return null;
}
return positionProperty.getStateValueAs(Double.class);
}
}

View File

@ -0,0 +1,41 @@
package de.ph87.home.shutter;
import com.fasterxml.jackson.annotation.JsonProperty;
import de.ph87.home.common.crud.AbstractSearchFilter;
import de.ph87.home.property.PropertyTypeMismatch;
import jakarta.annotation.Nullable;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
@Getter
@ToString
public class ShutterFilter extends AbstractSearchFilter {
@Nullable
@JsonProperty
private Boolean positionNull;
@Nullable
@JsonProperty
private Double positionMin;
@Nullable
@JsonProperty
private Double positionMax;
public boolean filter(@NonNull final ShutterDto dto) throws PropertyTypeMismatch {
if (positionNull != null && positionNull != (dto.getPositionProperty() == null)) {
return false;
}
final Double value = dto.getPositionValue();
if (positionMin != null && value != null && positionMin <= value) {
return false;
}
if (positionMax != null && value != null && positionMax >= value) {
return false;
}
return search(dto.getName());
}
}

View File

@ -0,0 +1,15 @@
package de.ph87.home.shutter;
import lombok.NonNull;
import org.springframework.data.repository.ListCrudRepository;
import java.util.List;
import java.util.Optional;
public interface ShutterRepository extends ListCrudRepository<Shutter, String> {
Optional<Shutter> findByUuidOrSlug(@NonNull String uuid, @NonNull String slug);
List<Shutter> findAllByPositionPropertyId(@NonNull String propertyId);
}

View File

@ -0,0 +1,95 @@
package de.ph87.home.shutter;
import de.ph87.home.common.crud.CrudAction;
import de.ph87.home.common.crud.EntityNotFound;
import de.ph87.home.property.*;
import jakarta.annotation.Nullable;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class ShutterService {
private final PropertyService propertyService;
private final ShutterRepository shutterRepository;
private final ApplicationEventPublisher applicationEventPublisher;
@NonNull
public ShutterDto create(@NonNull final String name, @NonNull final String slug, @NonNull final String positionProperty) {
return publish(shutterRepository.save(new Shutter(name, slug, positionProperty)), CrudAction.CREATED);
}
public void setPosition(@NonNull final String uuidOrSlug, final double position) throws PropertyNotFound, PropertyNotWritable, PropertyTypeMismatch {
log.debug("setPosition: uuidOrSlug={}, position={}", uuidOrSlug, position);
final Shutter shutter = getByUuidOrSlug(uuidOrSlug);
propertyService.write(shutter.getPositionPropertyId(), position, Double.class);
}
@NonNull
public ShutterDto getByUuidOrSlugDto(final @NonNull String uuidOrSlug) {
return toDto(getByUuidOrSlug(uuidOrSlug));
}
@NonNull
private Shutter getByUuidOrSlug(@NonNull final String uuidOrSlug) {
return shutterRepository.findByUuidOrSlug(uuidOrSlug, uuidOrSlug).orElseThrow(() -> new EntityNotFound("uuidOrSlug", uuidOrSlug));
}
@NonNull
public ShutterDto toDto(@NonNull final Shutter shutter) {
final PropertyDto<Double> position = propertyService.dtoByIdAndTypeOrNull(shutter.getPositionPropertyId(), Double.class);
return new ShutterDto(shutter, position);
}
@NonNull
private Shutter getByUuid(@NonNull final String uuid) {
return shutterRepository.findById(uuid).orElseThrow(() -> new EntityNotFound("uuid", uuid));
}
@NonNull
public List<ShutterDto> list(@Nullable final ShutterFilter filter) throws PropertyTypeMismatch {
final List<ShutterDto> all = shutterRepository.findAll().stream().map(this::toDto).toList();
if (filter == null) {
return all;
}
final List<ShutterDto> results = new ArrayList<>();
for (final ShutterDto dto : all) {
if (filter.filter(dto)) {
results.add(dto);
}
}
return results;
}
@EventListener(PropertyDto.class)
public void onPropertyChange(@NonNull final PropertyDto<?> dto) {
shutterRepository.findAllByPositionPropertyId(dto.getId()).forEach(shutter -> publish(shutter, CrudAction.UPDATED));
}
@NonNull
private ShutterDto publish(@NonNull final Shutter shutter, @NonNull final CrudAction action) {
final ShutterDto dto = toDto(shutter);
log.info("Shutter {}: {}", action, dto);
applicationEventPublisher.publishEvent(dto);
return dto;
}
@NonNull
public ShutterDto getByUuidDto(@NonNull final String uuid) {
return toDto(getByUuid(uuid));
}
}