diff --git a/mpaste/pom.xml b/mpaste/pom.xml index 9ffa22b..a3a0257 100644 --- a/mpaste/pom.xml +++ b/mpaste/pom.xml @@ -31,6 +31,14 @@ org.springframework.boot spring-boot-starter-data-jpa + + org.springframework.boot + spring-boot-starter-websocket + + + org.springframework.boot + spring-boot-starter-data-redis + org.mariadb.jdbc mariadb-java-client @@ -60,6 +68,14 @@ 1.1.1 compile + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + diff --git a/mpaste/src/main/java/net/miarma/backend/mpaste/config/RedisConfig.java b/mpaste/src/main/java/net/miarma/backend/mpaste/config/RedisConfig.java new file mode 100644 index 0000000..52af625 --- /dev/null +++ b/mpaste/src/main/java/net/miarma/backend/mpaste/config/RedisConfig.java @@ -0,0 +1,27 @@ +package net.miarma.backend.mpaste.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializer; + +@Configuration +public class RedisConfig { + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + + RedisSerializer jsonSerializer = RedisSerializer.json(); + + template.setKeySerializer(RedisSerializer.string()); + template.setValueSerializer(jsonSerializer); + template.setHashKeySerializer(RedisSerializer.string()); + template.setHashValueSerializer(jsonSerializer); + + template.afterPropertiesSet(); + return template; + } +} \ No newline at end of file diff --git a/mpaste/src/main/java/net/miarma/backend/mpaste/config/SchedulingConfig.java b/mpaste/src/main/java/net/miarma/backend/mpaste/config/SchedulingConfig.java deleted file mode 100644 index dca4f1a..0000000 --- a/mpaste/src/main/java/net/miarma/backend/mpaste/config/SchedulingConfig.java +++ /dev/null @@ -1,9 +0,0 @@ -package net.miarma.backend.mpaste.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.EnableScheduling; - -@EnableScheduling -@Configuration -public class SchedulingConfig { -} \ No newline at end of file diff --git a/mpaste/src/main/java/net/miarma/backend/mpaste/config/WebSocketConfig.java b/mpaste/src/main/java/net/miarma/backend/mpaste/config/WebSocketConfig.java new file mode 100644 index 0000000..8021ab2 --- /dev/null +++ b/mpaste/src/main/java/net/miarma/backend/mpaste/config/WebSocketConfig.java @@ -0,0 +1,24 @@ +package net.miarma.backend.mpaste.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; + +@Configuration +@EnableWebSocketMessageBroker +public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { + @Override + public void configureMessageBroker(MessageBrokerRegistry config) { + config.enableSimpleBroker("/topic"); + config.setApplicationDestinationPrefixes("/app"); + } + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/ws") + .setAllowedOriginPatterns("*") + .withSockJS(); + } +} diff --git a/mpaste/src/main/java/net/miarma/backend/mpaste/controller/PasteController.java b/mpaste/src/main/java/net/miarma/backend/mpaste/controller/PasteController.java index 2fdb89f..2eaefc4 100644 --- a/mpaste/src/main/java/net/miarma/backend/mpaste/controller/PasteController.java +++ b/mpaste/src/main/java/net/miarma/backend/mpaste/controller/PasteController.java @@ -1,7 +1,6 @@ package net.miarma.backend.mpaste.controller; import net.miarma.backend.mpaste.dto.PasteDto; -import net.miarma.backend.mpaste.dto.PastePassword; import net.miarma.backend.mpaste.mapper.PasteMapper; import net.miarma.backend.mpaste.service.PasteService; import org.springframework.http.ResponseEntity; @@ -35,7 +34,7 @@ public class PasteController { ); } - @GetMapping("/{paste_key}") + @GetMapping("/s/{paste_key}") public ResponseEntity getByKey( @PathVariable("paste_key") String pasteKey, @RequestHeader(value = "X-Paste-Password", required = false) String password @@ -63,10 +62,6 @@ public class PasteController { @DeleteMapping("/{paste_id}") public ResponseEntity delete(@PathVariable("paste_id") UUID pasteId) { - return ResponseEntity.ok( - PasteMapper.toResponse( - pasteService.delete(pasteId) - ) - ); + throw new UnsupportedOperationException("Pastes cannot be deleted manually"); } } diff --git a/mpaste/src/main/java/net/miarma/backend/mpaste/controller/RTPasteController.java b/mpaste/src/main/java/net/miarma/backend/mpaste/controller/RTPasteController.java new file mode 100644 index 0000000..c1d1799 --- /dev/null +++ b/mpaste/src/main/java/net/miarma/backend/mpaste/controller/RTPasteController.java @@ -0,0 +1,42 @@ +package net.miarma.backend.mpaste.controller; + +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.messaging.handler.annotation.DestinationVariable; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.handler.annotation.SendTo; +import org.springframework.messaging.simp.annotation.SendToUser; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +@Controller +public class RTPasteController { + private final RedisTemplate template; + private static final String REDIS_PREFIX = "rt_paste:"; + + public RTPasteController(RedisTemplate template) { + this.template = template; + } + + @MessageMapping("/edit/{key}") + @SendTo("/topic/session/{key}") + public Map handleUpdate(@DestinationVariable("key") String key, Map payload) { + template.opsForValue().set(REDIS_PREFIX + key, payload, 24, TimeUnit.HOURS); + return payload; + } + + @MessageMapping("/join/{key}") + @SendTo("/topic/session/{key}") + public Map handleJoin(@DestinationVariable("key") String key) { + Object data = template.opsForValue().get(REDIS_PREFIX + key); + if (data instanceof Map) { + return (Map) data; + } + return Map.of("content", "", "syntax", "plaintext", "title", ""); + } +} diff --git a/mpaste/src/main/java/net/miarma/backend/mpaste/dto/PasteDto.java b/mpaste/src/main/java/net/miarma/backend/mpaste/dto/PasteDto.java index 98f937c..4406304 100644 --- a/mpaste/src/main/java/net/miarma/backend/mpaste/dto/PasteDto.java +++ b/mpaste/src/main/java/net/miarma/backend/mpaste/dto/PasteDto.java @@ -9,14 +9,24 @@ public class PasteDto { public static class Request { + private String pasteKey; private String title; private String content; private String syntax; private Boolean burnAfter; private Boolean isPrivate; + private Boolean isRt; private String password; + public String getPasteKey() { + return pasteKey; + } + + public void setPasteKey(String pasteKey) { + this.pasteKey = pasteKey; + } + public String getTitle() { return title; } @@ -41,7 +51,7 @@ public class PasteDto { this.syntax = syntax; } - public Boolean getBurnAfter() { + public Boolean isBurnAfter() { return burnAfter; } @@ -49,7 +59,7 @@ public class PasteDto { this.burnAfter = burnAfter; } - public Boolean getIsPrivate() { + public Boolean isPrivate() { return isPrivate; } @@ -57,6 +67,14 @@ public class PasteDto { this.isPrivate = isPrivate; } + public Boolean isRt() { + return isRt; + } + + public void setRt(Boolean rt) { + isRt = rt; + } + public String getPassword() { return password; } @@ -79,6 +97,7 @@ public class PasteDto { private Boolean burnAfter; private Boolean isPrivate; + private Boolean isRt; private Instant createdAt; @@ -130,7 +149,7 @@ public class PasteDto { this.views = views; } - public Boolean getBurnAfter() { + public Boolean isBurnAfter() { return burnAfter; } @@ -138,14 +157,22 @@ public class PasteDto { this.burnAfter = burnAfter; } - public Boolean getIsPrivate() { + public Boolean isPrivate() { return isPrivate; } - public void setIsPrivate(Boolean isPrivate) { + public void setPrivate(Boolean isPrivate) { this.isPrivate = isPrivate; } + public Boolean isRt() { + return isRt; + } + + public void setRt(Boolean rt) { + isRt = rt; + } + public Instant getCreatedAt() { return createdAt; } diff --git a/mpaste/src/main/java/net/miarma/backend/mpaste/dto/PastePassword.java b/mpaste/src/main/java/net/miarma/backend/mpaste/dto/PastePassword.java deleted file mode 100644 index 244eebb..0000000 --- a/mpaste/src/main/java/net/miarma/backend/mpaste/dto/PastePassword.java +++ /dev/null @@ -1,3 +0,0 @@ -package net.miarma.backend.mpaste.dto; - -public record PastePassword(String password) {} diff --git a/mpaste/src/main/java/net/miarma/backend/mpaste/mapper/PasteMapper.java b/mpaste/src/main/java/net/miarma/backend/mpaste/mapper/PasteMapper.java index 44b8861..5a503c4 100644 --- a/mpaste/src/main/java/net/miarma/backend/mpaste/mapper/PasteMapper.java +++ b/mpaste/src/main/java/net/miarma/backend/mpaste/mapper/PasteMapper.java @@ -16,9 +16,11 @@ public final class PasteMapper { paste.setTitle(request.getTitle()); paste.setContent(request.getContent()); paste.setSyntax(request.getSyntax()); + paste.setPasteKey(request.getPasteKey()); - paste.setBurnAfter(Boolean.TRUE.equals(request.getBurnAfter())); - paste.setPrivate(Boolean.TRUE.equals(request.getIsPrivate())); + paste.setBurnAfter(Boolean.TRUE.equals(request.isBurnAfter())); + paste.setPrivate(Boolean.TRUE.equals(request.isPrivate())); + paste.setRt(Boolean.TRUE.equals(request.isRt())); paste.setPassword(request.getPassword()); @@ -42,7 +44,8 @@ public final class PasteMapper { response.setViews(paste.getViews()); response.setBurnAfter(paste.isBurnAfter()); - response.setIsPrivate(paste.isPrivate()); + response.setPrivate(paste.isPrivate()); + response.setRt(paste.isRt()); response.setCreatedAt(paste.getCreatedAt()); diff --git a/mpaste/src/main/java/net/miarma/backend/mpaste/model/Paste.java b/mpaste/src/main/java/net/miarma/backend/mpaste/model/Paste.java index 7c51eb5..94739e2 100644 --- a/mpaste/src/main/java/net/miarma/backend/mpaste/model/Paste.java +++ b/mpaste/src/main/java/net/miarma/backend/mpaste/model/Paste.java @@ -40,6 +40,9 @@ public class Paste { @Column(name = "is_private") private Boolean isPrivate; + @Column(name = "is_rt") + private Boolean isRt; + private String password; @Column(name = "created_at") @@ -136,10 +139,14 @@ public class Paste { return isPrivate; } - public void setPrivate(Boolean aPrivate) { - isPrivate = aPrivate; + public void setPrivate(Boolean isPrivate) { + this.isPrivate = isPrivate; } + public Boolean isRt() { return isRt; } + + public void setRt(Boolean rt) { isRt = rt; } + public String getPassword() { return password; } diff --git a/mpaste/src/main/java/net/miarma/backend/mpaste/service/PasteService.java b/mpaste/src/main/java/net/miarma/backend/mpaste/service/PasteService.java index 74df2d9..a61fa9c 100644 --- a/mpaste/src/main/java/net/miarma/backend/mpaste/service/PasteService.java +++ b/mpaste/src/main/java/net/miarma/backend/mpaste/service/PasteService.java @@ -32,6 +32,7 @@ public class PasteService { public List getAll() { return pasteRepository.findAll().stream() .filter(p -> !Boolean.TRUE.equals(p.isPrivate())) + .filter(p -> !Boolean.TRUE.equals(p.isRt())) .toList(); } @@ -64,14 +65,26 @@ public class PasteService { public Paste create(Paste paste) { PasteValidator.validate(paste); - if (Boolean.TRUE.equals(paste.isPrivate()) && paste.getPassword() != null) { - String encodedPassword = passwordEncoder.encode(paste.getPassword()); - paste.setPassword(encodedPassword); - } + return pasteRepository.findByPasteKey(paste.getPasteKey()) + .map(existing -> { + existing.setContent(paste.getContent()); + existing.setSyntax(paste.getSyntax()); + existing.setTitle(paste.getTitle()); + return pasteRepository.save(existing); + }) + .orElseGet(() -> { + if (Boolean.TRUE.equals(paste.isPrivate()) && paste.getPassword() != null) { + paste.setPassword(passwordEncoder.encode(paste.getPassword())); + } - paste.setPasteId(UUID.randomUUID()); - paste.setPasteKey(PasteKeyGenerator.generate(6)); - return pasteRepository.save(paste); + paste.setPasteId(UUID.randomUUID()); + + if (paste.getPasteKey() == null || paste.getPasteKey().isEmpty()) { + paste.setPasteKey(PasteKeyGenerator.generate(6)); + } + + return pasteRepository.save(paste); + }); } public Paste update(UUID pasteId, Paste changes) { diff --git a/mpaste/src/main/java/net/miarma/backend/mpaste/validation/PasteValidator.java b/mpaste/src/main/java/net/miarma/backend/mpaste/validation/PasteValidator.java index ac28ac2..bbe17e4 100644 --- a/mpaste/src/main/java/net/miarma/backend/mpaste/validation/PasteValidator.java +++ b/mpaste/src/main/java/net/miarma/backend/mpaste/validation/PasteValidator.java @@ -5,6 +5,14 @@ import net.miarma.backlib.exception.ValidationException; public class PasteValidator { public static void validate(Paste paste) { + if (Boolean.TRUE.equals(paste.isRt())) { + if (paste.getTitle() == null || paste.getTitle().trim().isEmpty()) { + String uuidStr = paste.getPasteId().toString(); + String shortId = uuidStr.substring(0, 8); + paste.setTitle("Sesión: " + shortId); + } + } + if (paste.getTitle() == null || paste.getTitle().trim().isEmpty()) { throw new ValidationException("title", "The title cannot be empty"); } diff --git a/mpaste/src/main/resources/application.yml b/mpaste/src/main/resources/application.yml index e563b0f..7637223 100644 --- a/mpaste/src/main/resources/application.yml +++ b/mpaste/src/main/resources/application.yml @@ -15,6 +15,11 @@ spring: default-property-inclusion: non_null time-zone: Europe/Madrid + data: + redis: + host: localhost + port: 6379 + jwt: expiration-ms: 3600000