rpg initial commit
All checks were successful
Build Org Backend / build (push) Successful in 10s

This commit is contained in:
2026-05-09 22:38:21 +01:00
parent 0e5f8d7b1c
commit b7ef8fc870
3 changed files with 139 additions and 0 deletions

View File

@@ -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();
}
}

View 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;
}
}

View 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();
}
}