Skip to content

Commit

Permalink
{REFACTOR} Refactored API and service-layer classes (#68)
Browse files Browse the repository at this point in the history
Refactored API by changing:

changed PUT /plaver/device/connect - to POST /player/devices
changed PUT /player/device/switch to PUT /player/device
Moved creation of Command classes from controller to HandlerMethodArgumentResolver(s).

Moved methods that used to manipulate with player from entity to domain model(CurrentPlayerState).
  • Loading branch information
justJavaProgrammer authored Jul 30, 2024
1 parent 5a2458d commit 59f8d48
Show file tree
Hide file tree
Showing 49 changed files with 924 additions and 327 deletions.
4 changes: 2 additions & 2 deletions docs/How-To-Connect-Device.MD
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Connect device to the player. User can then switch playback to this device.
#### Request

```http request
PUT /player/device/connect
POST /player/devices
Authorization: Bearer user_access_token
```

Expand Down Expand Up @@ -38,5 +38,5 @@ Status:
reason_code' in body, that can be used to determine type of the error
- 500 Server Error - should never happen, but if so - please, create a new issue that can be used to reproduce the issue

See the [tests](../src/test/java/com/odeyalo/sonata/connect/controller/ConnectDevicePlayerStateControllerTest.java)
See the [tests](../src/test/java/com/odeyalo/sonata/connect/controller/ConnectDeviceEndpointTest.java)
for further info
2 changes: 1 addition & 1 deletion docs/How-To-Disconnect-Device.MD
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Note: Do nothing if device by provided ID does not exist.
#### Request

```http request
DELETE /player/device
DELETE /player/devices
Authorization: Bearer user_access_token
```

Expand Down
2 changes: 1 addition & 1 deletion docs/How-To-Fetch-Connected-Devices.MD
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,4 @@ Array of the DeviceObject.
```
</details>

See the [tests](../src/test/java/com/odeyalo/sonata/connect/controller/AvailableDevicesPlayerStateControllerTest.java) for further info.
See the [tests](../src/test/java/com/odeyalo/sonata/connect/controller/FetchAvailableDevicesEndpointTest.java) for further info.
4 changes: 2 additions & 2 deletions docs/How-To-Switch-Devices.MD
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Transfer playback to a new device.
#### Request

```http request
PUT /player/device/switch
PUT /player/devices
Authorization: Bearer user_access_token
```

Expand All @@ -32,4 +32,4 @@ Status:
- 400 Bad Request - there is a problem in request or request body. All responses with this status code have 'reason_code' and small description about the error.
- 500 Server Error - should never happen, but if so - please, create a new GitHub issue that can be used to reproduce the issue

See the [tests](../src/test/java/com/odeyalo/sonata/connect/controller/DeviceEntitySwitchPlayerControllerTests.java) for further info
See the [tests](../src/test/java/com/odeyalo/sonata/connect/controller/SwitchDevicesEndpointTest.java) for further info
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.odeyalo.sonata.connect.config;


import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.codec.Decoder;
import org.springframework.http.codec.DecoderHttpMessageReader;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.json.Jackson2JsonDecoder;

@Configuration
public class WebfluxConfiguration {

@Bean
public Decoder<?> jackson2JsonDecoder(ObjectMapper mapper) {
return new Jackson2JsonDecoder(mapper);
}

@Bean
public HttpMessageReader<?> httpMessageReader(Decoder<?> decoder) {
return new DecoderHttpMessageReader<>(decoder);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.odeyalo.sonata.connect.controller;

import com.odeyalo.sonata.connect.model.Device;
import com.odeyalo.sonata.connect.model.User;
import com.odeyalo.sonata.connect.service.player.DeviceOperations;
import com.odeyalo.sonata.connect.service.player.DisconnectDeviceArgs;
import com.odeyalo.sonata.connect.service.player.SwitchDeviceCommandArgs;
import com.odeyalo.sonata.connect.service.player.TargetDeactivationDevices;
import com.odeyalo.sonata.connect.service.player.sync.TargetDevices;
import com.odeyalo.sonata.connect.service.support.mapper.dto.AvailableDevicesResponseDtoConverter;
import com.odeyalo.sonata.connect.support.web.HttpStatus;
import com.odeyalo.sonata.connect.support.web.annotation.ConnectionTarget;
import com.odeyalo.sonata.connect.support.web.annotation.TransferPlaybackTargets;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

@RestController
@RequestMapping("/player")
@RequiredArgsConstructor
public class DevicesController {
private final DeviceOperations deviceOperations;
private final AvailableDevicesResponseDtoConverter availableDevicesConverter;

@GetMapping(value = "/devices", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<ResponseEntity<?>> getAvailableDevices(@NotNull final User user) {

return deviceOperations.getConnectedDevices(user)
.map(availableDevicesConverter::convertTo)
.subscribeOn(Schedulers.boundedElastic())
.map(HttpStatus::ok);
}

@PostMapping(value = "/devices", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<ResponseEntity<?>> addDevice(@NotNull final User user,
@NotNull @ConnectionTarget final Device device) {

return deviceOperations.addDevice(user, device)
.subscribeOn(Schedulers.boundedElastic())
.thenReturn(HttpStatus.default204Response());
}

@PutMapping(value = "/devices", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<ResponseEntity<?>> switchDevices(@NotNull final User user,
@NotNull final SwitchDeviceCommandArgs switchDeviceCommandArgs,
@NotNull final TargetDeactivationDevices targetDeactivationDevices,
@NotNull @TransferPlaybackTargets final TargetDevices transferPlaybackTargets) {
return deviceOperations.transferPlayback(
user,
switchDeviceCommandArgs,
targetDeactivationDevices,
transferPlaybackTargets)
.subscribeOn(Schedulers.boundedElastic())
.thenReturn(HttpStatus.default204Response());
}

@DeleteMapping(value = "/devices", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<ResponseEntity<?>> disconnectDevice(@NotNull final DisconnectDeviceArgs disconnectDeviceArgs,
@NotNull final User user) {
return deviceOperations.disconnectDevice(user, disconnectDeviceArgs)
.subscribeOn(Schedulers.boundedElastic())
.thenReturn(HttpStatus.default204Response());
}
}
Original file line number Diff line number Diff line change
@@ -1,125 +1,80 @@
package com.odeyalo.sonata.connect.controller;

import com.odeyalo.sonata.connect.dto.*;
import com.odeyalo.sonata.connect.model.*;
import com.odeyalo.sonata.connect.service.player.*;
import com.odeyalo.sonata.connect.service.player.sync.TargetDevices;
import com.odeyalo.sonata.connect.dto.CurrentlyPlayingPlayerStateDto;
import com.odeyalo.sonata.connect.dto.PlayerStateDto;
import com.odeyalo.sonata.connect.model.CurrentlyPlayingPlayerState;
import com.odeyalo.sonata.connect.model.ShuffleMode;
import com.odeyalo.sonata.connect.model.User;
import com.odeyalo.sonata.connect.model.Volume;
import com.odeyalo.sonata.connect.service.player.BasicPlayerOperations;
import com.odeyalo.sonata.connect.service.player.PlayCommandContext;
import com.odeyalo.sonata.connect.service.support.mapper.Converter;
import com.odeyalo.sonata.connect.service.support.mapper.dto.ConnectDeviceRequest2DeviceConverter;
import com.odeyalo.sonata.connect.service.support.mapper.dto.CurrentPlayerState2PlayerStateDtoConverter;
import com.odeyalo.sonata.connect.service.support.mapper.dto.Devices2DevicesDtoConverter;
import jakarta.validation.Valid;
import com.odeyalo.sonata.connect.support.web.HttpStatus;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

@RestController
@RequestMapping("/player")
@RequiredArgsConstructor
public class PlayerController {
public final class PlayerController {
private final BasicPlayerOperations playerOperations;
private final CurrentPlayerState2PlayerStateDtoConverter playerState2PlayerStateDtoConverter;
private final Devices2DevicesDtoConverter devicesDtoConverter;
private final ConnectDeviceRequest2DeviceConverter deviceModelConverter;
private final Converter<CurrentlyPlayingPlayerState, CurrentlyPlayingPlayerStateDto> currentlyPlayingPlayerStateDtoConverter;

@GetMapping("/state")
public Mono<PlayerStateDto> currentPlayerState(User user) {

@GetMapping(value = "/state", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<PlayerStateDto> currentPlayerState(@NotNull final User user) {
return playerOperations.currentState(user)
.subscribeOn(Schedulers.boundedElastic())
.map(playerState2PlayerStateDtoConverter::convertTo);
}

@GetMapping("/currently-playing")
public Mono<ResponseEntity<CurrentlyPlayingPlayerStateDto>> currentlyPlaying(User user) {
@GetMapping(value = "/currently-playing", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<ResponseEntity<CurrentlyPlayingPlayerStateDto>> currentlyPlaying(@NotNull final User user) {
return playerOperations.currentlyPlayingState(user)
.map(state -> ResponseEntity.ok(convertToCurrentlyPlayingStateDto(state)))
.defaultIfEmpty(ResponseEntity.noContent().build());
}

@GetMapping("/devices")
public Mono<ResponseEntity<?>> getAvailableDevices(User user) {
return playerOperations.getDeviceOperations().getConnectedDevices(user)
.map(this::convertToAvailableDevicesResponseDto)
.map(body -> ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(body));
.subscribeOn(Schedulers.boundedElastic())
.map(currentlyPlayingPlayerStateDtoConverter::convertTo)
.map(HttpStatus::ok)
.defaultIfEmpty(HttpStatus.default204Response());
}

@PutMapping("/play")
public Mono<ResponseEntity<?>> playOrResume(User user, @RequestBody PlayResumePlaybackRequest body) {
return playerOperations.playOrResume(user, PlayCommandContext.of(body.getContextUri()), null)
.thenReturn(default204Response());
@PutMapping(value = "/play", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<ResponseEntity<?>> playOrResume(@NotNull final User user,
@NotNull final PlayCommandContext commandContext) {
return playerOperations.playOrResume(user, commandContext, null)
.subscribeOn(Schedulers.boundedElastic())
.thenReturn(HttpStatus.default204Response());
}

@PutMapping("/pause")
public Mono<ResponseEntity<?>> pause(User user) {
return playerOperations.pause(user).thenReturn(
default204Response()
);
@PutMapping(value = "/pause", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<ResponseEntity<?>> pause(@NotNull final User user) {
return playerOperations.pause(user)
.subscribeOn(Schedulers.boundedElastic())
.thenReturn(HttpStatus.default204Response());
}

@PutMapping("/shuffle")
@PutMapping(value = "/shuffle", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<ResponseEntity<?>> changeShuffleMode(@NotNull final User user,
@NotNull final ShuffleMode shuffleMode) {

return playerOperations.changeShuffle(user, shuffleMode)
.subscribeOn(Schedulers.boundedElastic())
.map(playerState -> default204Response());
}

@PutMapping(value = "/device/connect", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<ResponseEntity<?>> addDevice(User user,
@Valid @RequestBody ConnectDeviceRequest body) {
Device device = deviceModelConverter.convertTo(body);

return playerOperations.getDeviceOperations().addDevice(user, device)
.thenReturn(default204Response());
}

@PutMapping(value = "/device/switch", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<ResponseEntity<?>> switchDevices(User user, @RequestBody DeviceSwitchRequest body) {
return playerOperations.getDeviceOperations().transferPlayback(
user,
SwitchDeviceCommandArgs.noMatter(),
TargetDeactivationDevices.empty(),
TargetDevices.fromDeviceIds(body.getDeviceIds()))
.thenReturn(default204Response());
.map(playerState -> HttpStatus.default204Response());
}

@PutMapping(value = "/volume", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<ResponseEntity<?>> changePlayerVolume(@NotNull final Volume volume,
@NotNull final User user) {
return playerOperations.changeVolume(user, volume)
.map(it -> default204Response());
}

@DeleteMapping(value = "/device")
public Mono<ResponseEntity<?>> disconnectDevice(@RequestParam("device_id") String deviceId, User user) {
return playerOperations.getDeviceOperations()
.disconnectDevice(user, DisconnectDeviceArgs.withDeviceId(deviceId))
.thenReturn(default204Response());
}

@NotNull
private CurrentlyPlayingPlayerStateDto convertToCurrentlyPlayingStateDto(CurrentlyPlayingPlayerState state) {
return currentlyPlayingPlayerStateDtoConverter.convertTo(state);
}

@NotNull
private AvailableDevicesResponseDto convertToAvailableDevicesResponseDto(Devices devices) {
DevicesDto devicesDto = devicesDtoConverter.convertTo(devices);
return AvailableDevicesResponseDto.of(devicesDto);
}

@NotNull
private static ResponseEntity<?> default204Response() {
return ResponseEntity.noContent()
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
.subscribeOn(Schedulers.boundedElastic())
.map(it -> HttpStatus.default204Response());
}
}
Loading

0 comments on commit 59f8d48

Please sign in to comment.