This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
package org.zaine.app.controller;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.zaine.app.dto.RpgSaveDTO;
|
||||
import org.zaine.app.service.RpgSaveService;
|
||||
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/play/rpg")
|
||||
@Tag(name = "RPG Saves", description = "Persistent save slots for browser RPGs")
|
||||
public class RpgSaveController {
|
||||
private final RpgSaveService rpgSaveService;
|
||||
|
||||
public RpgSaveController(RpgSaveService rpgSaveService) {
|
||||
this.rpgSaveService = rpgSaveService;
|
||||
}
|
||||
|
||||
@GetMapping("/save/{slot}")
|
||||
public ResponseEntity<RpgSaveDTO> load(@PathVariable String slot) {
|
||||
RpgSaveDTO save = rpgSaveService.load(slot);
|
||||
if (save == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
return ResponseEntity.ok(save);
|
||||
}
|
||||
|
||||
@PutMapping("/save/{slot}")
|
||||
public ResponseEntity<RpgSaveDTO> save(@PathVariable String slot, @RequestBody RpgSaveDTO dto) {
|
||||
return ResponseEntity.ok(rpgSaveService.save(slot, dto.getPayload()));
|
||||
}
|
||||
|
||||
@DeleteMapping("/save/{slot}")
|
||||
public ResponseEntity<Void> delete(@PathVariable String slot) {
|
||||
rpgSaveService.delete(slot);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
24
src/main/java/org/zaine/app/dto/RpgSaveDTO.java
Normal file
24
src/main/java/org/zaine/app/dto/RpgSaveDTO.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package org.zaine.app.dto;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
public class RpgSaveDTO {
|
||||
private String slot;
|
||||
private JsonNode payload;
|
||||
|
||||
public String getSlot() {
|
||||
return slot;
|
||||
}
|
||||
|
||||
public void setSlot(String slot) {
|
||||
this.slot = slot;
|
||||
}
|
||||
|
||||
public JsonNode getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
public void setPayload(JsonNode payload) {
|
||||
this.payload = payload;
|
||||
}
|
||||
}
|
||||
70
src/main/java/org/zaine/app/service/RpgSaveService.java
Normal file
70
src/main/java/org/zaine/app/service/RpgSaveService.java
Normal file
@@ -0,0 +1,70 @@
|
||||
package org.zaine.app.service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.zaine.app.dto.RpgSaveDTO;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
@Service
|
||||
public class RpgSaveService {
|
||||
private final ObjectMapper objectMapper;
|
||||
private final Path saveDirectory;
|
||||
|
||||
public RpgSaveService(
|
||||
ObjectMapper objectMapper,
|
||||
@Value("${play.rpg.save-dir:${user.home}/.org-web-rpg-saves}") String saveDirectory) {
|
||||
this.objectMapper = objectMapper;
|
||||
this.saveDirectory = Path.of(saveDirectory);
|
||||
}
|
||||
|
||||
public RpgSaveDTO load(String slot) {
|
||||
Path file = savePath(slot);
|
||||
if (!Files.exists(file)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(file.toFile(), RpgSaveDTO.class);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to load RPG save", e);
|
||||
}
|
||||
}
|
||||
|
||||
public RpgSaveDTO save(String slot, JsonNode payload) {
|
||||
try {
|
||||
Files.createDirectories(saveDirectory);
|
||||
RpgSaveDTO dto = new RpgSaveDTO();
|
||||
dto.setSlot(slot);
|
||||
dto.setPayload(payload);
|
||||
|
||||
ObjectNode wrapped = objectMapper.createObjectNode();
|
||||
wrapped.put("slot", slot);
|
||||
wrapped.set("payload", payload);
|
||||
objectMapper.writerWithDefaultPrettyPrinter().writeValue(savePath(slot).toFile(), wrapped);
|
||||
return dto;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to save RPG progress", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void delete(String slot) {
|
||||
try {
|
||||
Files.deleteIfExists(savePath(slot));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to delete RPG save", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Path savePath(String slot) {
|
||||
if (slot == null || !slot.matches("[A-Za-z0-9_-]{1,64}")) {
|
||||
throw new IllegalArgumentException("Invalid RPG save slot");
|
||||
}
|
||||
return saveDirectory.resolve(slot + ".json").normalize();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user