Nuke again (last time I swear) #6
@@ -4,9 +4,9 @@ plugins {
|
||||
id 'io.spring.dependency-management' version '1.1.7'
|
||||
}
|
||||
|
||||
group = 'net.kpuig.concord'
|
||||
group = 'net.kpuig.shoebill'
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
description = 'Concord REST Server'
|
||||
description = 'Shoebill REST Server'
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
@@ -30,6 +30,7 @@ dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-webmvc'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-websocket'
|
||||
implementation 'org.springframework.boot:spring-boot-h2console'
|
||||
implementation 'org.springframework.boot:spring-boot-autoconfigure-processor'
|
||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.3'
|
||||
//implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
//implementation 'org.springframework.boot:spring-boot-starter-security-oauth2-resource-server'
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package net.kpuig.concord.backend;
|
||||
package net.kpuig.shoebill.backend;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
|
||||
import net.kpuig.shoebill.backend.config.ShoebillConfiguration;
|
||||
|
||||
@EnableConfigurationProperties(ShoebillConfiguration.class)
|
||||
@SpringBootApplication
|
||||
public class BackendApplication {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package net.kpuig.shoebill.backend.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.bind.DefaultValue;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
@ConfigurationProperties(prefix = "shoebill")
|
||||
@Validated
|
||||
public record ShoebillConfiguration (
|
||||
@DefaultValue("false")
|
||||
boolean relativePicturesOnly
|
||||
) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package net.kpuig.shoebill.backend.controllers;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
import jakarta.validation.Valid;
|
||||
import net.kpuig.shoebill.backend.datamodels.server.GetAllServersResponse;
|
||||
import net.kpuig.shoebill.backend.datamodels.server.GetServerResponse;
|
||||
import net.kpuig.shoebill.backend.datamodels.server.PostServerRequest;
|
||||
import net.kpuig.shoebill.backend.datamodels.server.PostServerResponse;
|
||||
import net.kpuig.shoebill.backend.services.ServerService;
|
||||
import net.kpuig.shoebill.backend.services.exceptions.BadRequestException;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/server")
|
||||
public class ServerController {
|
||||
@Autowired private ServerService serverService;
|
||||
|
||||
@Operation(summary = "Get all servers", description = "Retrieves a list of all servers")
|
||||
@ApiResponse(responseCode = "200", description = "Servers retrieved")
|
||||
@GetMapping("/")
|
||||
public ResponseEntity<GetAllServersResponse> getAllServers() {
|
||||
return ResponseEntity.ok(serverService.getAllServers());
|
||||
}
|
||||
|
||||
@Operation(summary = "Get server by ID", description = "Retrieves a server by its ID")
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "Server retrieved"),
|
||||
@ApiResponse(responseCode = "404", description = "Server not found")
|
||||
})
|
||||
@GetMapping("")
|
||||
public ResponseEntity<GetServerResponse> getServerById(@RequestParam(name = "id", required = false) Long id) {
|
||||
try {
|
||||
return ResponseEntity.ok(serverService.getServerById(id));
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "Create a new server", description = "Creates a new server with the provided name and image")
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "201", description = "Server created"),
|
||||
@ApiResponse(responseCode = "400", description = "Invalid request")
|
||||
})
|
||||
@PostMapping("/")
|
||||
public ResponseEntity<PostServerResponse> postMethodName(@Valid @RequestBody PostServerRequest request) {
|
||||
try {
|
||||
var response = serverService.createServer(request);
|
||||
return ResponseEntity.ok(response);
|
||||
} catch (BadRequestException e) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.kpuig.concord.backend.controllers;
|
||||
package net.kpuig.shoebill.backend.controllers;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
@@ -17,18 +17,15 @@ import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
import jakarta.validation.Valid;
|
||||
import net.kpuig.concord.backend.datamodels.userprofile.GetAllUserProfilesResponse;
|
||||
import net.kpuig.concord.backend.datamodels.userprofile.GetUserProfileResponse;
|
||||
import net.kpuig.concord.backend.datamodels.userprofile.PostUserProfileRequest;
|
||||
import net.kpuig.concord.backend.datamodels.userprofile.PostUserProfileResponse;
|
||||
import net.kpuig.concord.backend.services.UserProfileService;
|
||||
import net.kpuig.concord.backend.services.exceptions.BadRequestException;
|
||||
|
||||
|
||||
|
||||
import net.kpuig.shoebill.backend.datamodels.userprofile.GetAllUserProfilesResponse;
|
||||
import net.kpuig.shoebill.backend.datamodels.userprofile.GetUserProfileResponse;
|
||||
import net.kpuig.shoebill.backend.datamodels.userprofile.PostUserProfileRequest;
|
||||
import net.kpuig.shoebill.backend.datamodels.userprofile.PostUserProfileResponse;
|
||||
import net.kpuig.shoebill.backend.services.UserProfileService;
|
||||
import net.kpuig.shoebill.backend.services.exceptions.BadRequestException;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/user-profile")
|
||||
@RequestMapping("/api/user-profile")
|
||||
public class UserProfileController {
|
||||
@Autowired
|
||||
private UserProfileService userProfileService;
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.kpuig.concord.backend.datamodels.channel;
|
||||
package net.kpuig.shoebill.backend.datamodels.channel;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
@@ -6,7 +6,7 @@ import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import lombok.Data;
|
||||
import net.kpuig.concord.backend.datamodels.server.Server;
|
||||
import net.kpuig.shoebill.backend.datamodels.server.Server;
|
||||
|
||||
@Data
|
||||
@Entity(name = "channel")
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.kpuig.concord.backend.datamodels.channel;
|
||||
package net.kpuig.shoebill.backend.datamodels.channel;
|
||||
|
||||
public enum ChannelType {
|
||||
TEXT_CHANNEL,
|
||||
@@ -1,13 +1,14 @@
|
||||
package net.kpuig.concord.backend.datamodels.message;
|
||||
package net.kpuig.shoebill.backend.datamodels.message;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import lombok.Data;
|
||||
import net.kpuig.concord.backend.datamodels.channel.Channel;
|
||||
import net.kpuig.concord.backend.datamodels.userprofile.UserProfile;
|
||||
import net.kpuig.shoebill.backend.datamodels.channel.Channel;
|
||||
import net.kpuig.shoebill.backend.datamodels.userprofile.UserProfile;
|
||||
|
||||
@Data
|
||||
@Entity(name = "message")
|
||||
@@ -20,6 +21,9 @@ public class Message {
|
||||
|
||||
private Long timestamp;
|
||||
|
||||
@OneToOne
|
||||
private Message replyTo;
|
||||
|
||||
@ManyToOne
|
||||
private UserProfile userProfile;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package net.kpuig.shoebill.backend.datamodels.server;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class GetAllServersResponse {
|
||||
private List<GetServerResponse> servers;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package net.kpuig.shoebill.backend.datamodels.server;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class GetServerResponse {
|
||||
private Long id;
|
||||
private String name;
|
||||
private URI image;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package net.kpuig.shoebill.backend.datamodels.server;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class PostServerRequest {
|
||||
@NotBlank
|
||||
private String name;
|
||||
|
||||
private URI image;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package net.kpuig.shoebill.backend.datamodels.server;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class PostServerResponse {
|
||||
private Long id;
|
||||
private String name;
|
||||
private URI image;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package net.kpuig.concord.backend.datamodels.server;
|
||||
package net.kpuig.shoebill.backend.datamodels.server;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
@@ -17,5 +17,5 @@ public class Server {
|
||||
|
||||
private String name;
|
||||
|
||||
private URL image;
|
||||
private URI image;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.kpuig.concord.backend.datamodels.userprofile;
|
||||
package net.kpuig.shoebill.backend.datamodels.userprofile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.kpuig.concord.backend.datamodels.userprofile;
|
||||
package net.kpuig.shoebill.backend.datamodels.userprofile;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.kpuig.concord.backend.datamodels.userprofile;
|
||||
package net.kpuig.shoebill.backend.datamodels.userprofile;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.kpuig.concord.backend.datamodels.userprofile;
|
||||
package net.kpuig.shoebill.backend.datamodels.userprofile;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.kpuig.concord.backend.datamodels.userprofile;
|
||||
package net.kpuig.shoebill.backend.datamodels.userprofile;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
@@ -1,9 +1,9 @@
|
||||
package net.kpuig.concord.backend.repositories;
|
||||
package net.kpuig.shoebill.backend.repositories;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import net.kpuig.concord.backend.datamodels.channel.Channel;
|
||||
import net.kpuig.shoebill.backend.datamodels.channel.Channel;
|
||||
|
||||
@Repository
|
||||
public interface ChannelRepository extends JpaRepository<Channel, Long> {
|
||||
@@ -1,9 +1,9 @@
|
||||
package net.kpuig.concord.backend.repositories;
|
||||
package net.kpuig.shoebill.backend.repositories;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import net.kpuig.concord.backend.datamodels.message.Message;
|
||||
import net.kpuig.shoebill.backend.datamodels.message.Message;
|
||||
|
||||
@Repository
|
||||
public interface MessageRepository extends JpaRepository<Message, Long> {
|
||||
@@ -1,9 +1,9 @@
|
||||
package net.kpuig.concord.backend.repositories;
|
||||
package net.kpuig.shoebill.backend.repositories;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import net.kpuig.concord.backend.datamodels.server.Server;
|
||||
import net.kpuig.shoebill.backend.datamodels.server.Server;
|
||||
|
||||
@Repository
|
||||
public interface ServerRepository extends JpaRepository<Server, Long> {
|
||||
@@ -1,11 +1,11 @@
|
||||
package net.kpuig.concord.backend.repositories;
|
||||
package net.kpuig.shoebill.backend.repositories;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import net.kpuig.concord.backend.datamodels.userprofile.UserProfile;
|
||||
import net.kpuig.shoebill.backend.datamodels.userprofile.UserProfile;
|
||||
|
||||
@Repository
|
||||
public interface UserProfileRepository extends JpaRepository<UserProfile, Long> {
|
||||
@@ -0,0 +1,79 @@
|
||||
package net.kpuig.shoebill.backend.services;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import net.kpuig.shoebill.backend.config.ShoebillConfiguration;
|
||||
import net.kpuig.shoebill.backend.datamodels.server.GetAllServersResponse;
|
||||
import net.kpuig.shoebill.backend.datamodels.server.GetServerResponse;
|
||||
import net.kpuig.shoebill.backend.datamodels.server.PostServerRequest;
|
||||
import net.kpuig.shoebill.backend.datamodels.server.PostServerResponse;
|
||||
import net.kpuig.shoebill.backend.datamodels.server.Server;
|
||||
import net.kpuig.shoebill.backend.repositories.ServerRepository;
|
||||
import net.kpuig.shoebill.backend.services.exceptions.BadRequestException;
|
||||
import net.kpuig.shoebill.backend.services.exceptions.NotFoundException;
|
||||
|
||||
@Service
|
||||
public class ServerService {
|
||||
@Autowired private ShoebillConfiguration shoebillConfiguration;
|
||||
@Autowired private ServerRepository serverRepository;
|
||||
|
||||
public GetAllServersResponse getAllServers() {
|
||||
GetAllServersResponse response = new GetAllServersResponse();
|
||||
response.setServers(serverRepository.findAll().stream().map(server -> {
|
||||
GetServerResponse serverResponse = new GetServerResponse();
|
||||
serverResponse.setId(server.getId());
|
||||
serverResponse.setName(server.getName());
|
||||
serverResponse.setImage(server.getImage());
|
||||
return serverResponse;
|
||||
}).toList());
|
||||
return response;
|
||||
}
|
||||
|
||||
public GetServerResponse getServerById(Long id) {
|
||||
GetServerResponse response = new GetServerResponse();
|
||||
serverRepository.findById(id).ifPresentOrElse(server -> {
|
||||
response.setId(server.getId());
|
||||
response.setName(server.getName());
|
||||
response.setImage(server.getImage());
|
||||
}, () -> {
|
||||
throw new NotFoundException("Server not found with id: " + id);
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
public PostServerResponse createServer(PostServerRequest request) {
|
||||
Server server = new Server();
|
||||
server.setName(request.getName());
|
||||
server.setImage(request.getImage());
|
||||
|
||||
try {
|
||||
server = serverRepository.save(server);
|
||||
var uri = new URI(request.getImage().toString());
|
||||
|
||||
// Make sure it's either relative with no injections or a complete HTTP/HTTPS URL
|
||||
if (uri.isAbsolute()) {
|
||||
if (shoebillConfiguration.relativePicturesOnly()) {
|
||||
throw new BadRequestException("Only relative image URLs are allowed");
|
||||
}
|
||||
if (!uri.getScheme().equals("http") && !uri.getScheme().equals("https")) {
|
||||
throw new BadRequestException("Image URL must be HTTP or HTTPS");
|
||||
}
|
||||
} else {
|
||||
if (request.getImage().toString().contains("..") || request.getImage().toString().contains("//") || request.getImage().toString().contains("\\")) {
|
||||
throw new BadRequestException("Relative image URL cannot contain '..', '//', or '\\'");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new BadRequestException("Invalid image URL");
|
||||
}
|
||||
|
||||
PostServerResponse response = new PostServerResponse();
|
||||
response.setId(server.getId());
|
||||
response.setName(server.getName());
|
||||
response.setImage(server.getImage());
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
package net.kpuig.concord.backend.services;
|
||||
package net.kpuig.shoebill.backend.services;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import net.kpuig.concord.backend.datamodels.userprofile.GetAllUserProfilesResponse;
|
||||
import net.kpuig.concord.backend.datamodels.userprofile.GetUserProfileResponse;
|
||||
import net.kpuig.concord.backend.datamodels.userprofile.PostUserProfileResponse;
|
||||
import net.kpuig.concord.backend.datamodels.userprofile.UserProfile;
|
||||
import net.kpuig.concord.backend.repositories.UserProfileRepository;
|
||||
import net.kpuig.concord.backend.services.exceptions.BadRequestException;
|
||||
import net.kpuig.shoebill.backend.datamodels.userprofile.GetAllUserProfilesResponse;
|
||||
import net.kpuig.shoebill.backend.datamodels.userprofile.GetUserProfileResponse;
|
||||
import net.kpuig.shoebill.backend.datamodels.userprofile.PostUserProfileResponse;
|
||||
import net.kpuig.shoebill.backend.datamodels.userprofile.UserProfile;
|
||||
import net.kpuig.shoebill.backend.repositories.UserProfileRepository;
|
||||
import net.kpuig.shoebill.backend.services.exceptions.BadRequestException;
|
||||
|
||||
@Service
|
||||
public class UserProfileService {
|
||||
@@ -32,7 +32,7 @@ public class UserProfileService {
|
||||
response.setId(userProfile.getId());
|
||||
response.setUsername(userProfile.getUsername());
|
||||
}, () -> {
|
||||
throw new net.kpuig.concord.backend.services.exceptions.NotFoundException("User profile not found");
|
||||
throw new net.kpuig.shoebill.backend.services.exceptions.NotFoundException("User profile not found");
|
||||
});
|
||||
return response;
|
||||
}
|
||||
@@ -43,7 +43,7 @@ public class UserProfileService {
|
||||
response.setId(userProfile.getId());
|
||||
response.setUsername(userProfile.getUsername());
|
||||
}, () -> {
|
||||
throw new net.kpuig.concord.backend.services.exceptions.NotFoundException("User profile not found");
|
||||
throw new net.kpuig.shoebill.backend.services.exceptions.NotFoundException("User profile not found");
|
||||
});
|
||||
return response;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.kpuig.concord.backend.services.exceptions;
|
||||
package net.kpuig.shoebill.backend.services.exceptions;
|
||||
|
||||
public class BadRequestException extends RuntimeException {
|
||||
public BadRequestException(String message) {
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.kpuig.concord.backend.services.exceptions;
|
||||
package net.kpuig.shoebill.backend.services.exceptions;
|
||||
|
||||
public class NotFoundException extends RuntimeException {
|
||||
public NotFoundException(String message) {
|
||||
@@ -12,4 +12,5 @@ server.port=8080
|
||||
springdoc.api-docs.enabled=true
|
||||
springdoc.api-docs.path=/v3/api-docs
|
||||
springdoc.swagger-ui.enabled=true
|
||||
springdoc.swagger-ui.path=/swag
|
||||
springdoc.swagger-ui.path=/swag
|
||||
shoebill.relative-pictures-only=true
|
||||
Reference in New Issue
Block a user