commit 749135a91f75ef8957fe9a9ad83485a771bf72e4 Author: Liam Date: Tue Apr 23 00:43:05 2024 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..010b430 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/application.yml b/application.yml new file mode 100644 index 0000000..658e786 --- /dev/null +++ b/application.yml @@ -0,0 +1,20 @@ +server: + address: 0.0.0.0 + port: 80 + servlet: + context-path: / + +# Spring Configuration +spring: + # Don't include null properties in JSON + jackson: + default-property-inclusion: non_null + data: + # MongoDB - This is used for general data storage + mongodb: + uri: mongodb://root:root@10.0.50.118:27017/ + database: paste + +paste: + # The length of the ID for the paste + id-length: 6 \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..5f5f41e --- /dev/null +++ b/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + + cc.fascinated + Paste-Backend + 1.0-SNAPSHOT + + + 17 + 17 + UTF-8 + + + + org.springframework.boot + spring-boot-starter-parent + 3.2.5 + + + + + + ${project.artifactId} + + + + org.springframework.boot + spring-boot-maven-plugin + + + build-info + + build-info + + + + ${project.description} + + + + + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + + + org.projectlombok + lombok + 1.18.32 + provided + + + org.apache.commons + commons-lang3 + 3.14.0 + compile + + + + \ No newline at end of file diff --git a/src/main/java/cc/fascinated/Main.java b/src/main/java/cc/fascinated/Main.java new file mode 100644 index 0000000..e364fbe --- /dev/null +++ b/src/main/java/cc/fascinated/Main.java @@ -0,0 +1,30 @@ +package cc.fascinated; + +import lombok.SneakyThrows; +import lombok.extern.log4j.Log4j2; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.Objects; + +@Log4j2(topic = "Main") +@SpringBootApplication +public class Main { + @SneakyThrows + public static void main(String[] args) { + File config = new File("application.yml"); + if (!config.exists()) { // Saving the default config if it doesn't exist locally + Files.copy(Objects.requireNonNull(Main.class.getResourceAsStream("/application.yml")), config.toPath(), StandardCopyOption.REPLACE_EXISTING); + log.info("Saved the default configuration to '{}', please re-launch the application", // Log the default config being saved + config.getAbsolutePath() + ); + return; + } + log.info("Found configuration at '{}'", config.getAbsolutePath()); // Log the found config + + SpringApplication.run(Main.class, args); // Start the application + } +} \ No newline at end of file diff --git a/src/main/java/cc/fascinated/controller/PasteController.java b/src/main/java/cc/fascinated/controller/PasteController.java new file mode 100644 index 0000000..50f45b0 --- /dev/null +++ b/src/main/java/cc/fascinated/controller/PasteController.java @@ -0,0 +1,40 @@ +package cc.fascinated.controller; + +import cc.fascinated.model.Paste; +import cc.fascinated.service.PasteService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@RestController +@RequestMapping(value = "/") +public class PasteController { + + private final PasteService pasteService; + + @Autowired + public PasteController(PasteService pasteService) { + this.pasteService = pasteService; + } + + @GetMapping(value = "/") + public ResponseEntity home() { + return ResponseEntity.ok(Map.of( + "status", "OK" + )); + } + + @PostMapping(value = "/upload") + public ResponseEntity uploadPaste(@RequestBody String content) { + String id = pasteService.createPaste(content); + return ResponseEntity.ok(Map.of("id", id)); + } + + @GetMapping(value = "/{id}") + public ResponseEntity getPaste(@PathVariable String id) { + Paste paste = pasteService.getPaste(id); + return ResponseEntity.ok(paste); + } +} diff --git a/src/main/java/cc/fascinated/exception/ExceptionControllerAdvice.java b/src/main/java/cc/fascinated/exception/ExceptionControllerAdvice.java new file mode 100644 index 0000000..74de241 --- /dev/null +++ b/src/main/java/cc/fascinated/exception/ExceptionControllerAdvice.java @@ -0,0 +1,45 @@ +package cc.fascinated.exception; + +import cc.fascinated.model.response.ErrorResponse; +import io.micrometer.common.lang.NonNull; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.servlet.resource.NoResourceFoundException; + +@ControllerAdvice +public final class ExceptionControllerAdvice { + + /** + * Handle a raised exception. + * + * @param ex the raised exception + * @return the error response + */ + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(@NonNull Exception ex) { + HttpStatus status = null; // Get the HTTP status + if (ex instanceof NoResourceFoundException) { // Not found + status = HttpStatus.NOT_FOUND; + } else if (ex instanceof UnsupportedOperationException) { // Not implemented + status = HttpStatus.NOT_IMPLEMENTED; + } + if (ex.getClass().isAnnotationPresent(ResponseStatus.class)) { // Get from the @ResponseStatus annotation + status = ex.getClass().getAnnotation(ResponseStatus.class).value(); + } + String message = ex.getLocalizedMessage(); // Get the error message + if (message == null) { // Fallback + message = "An internal error has occurred."; + } + // Print the stack trace if no response status is present + if (status == null) { + ex.printStackTrace(); + } + if (status == null) { // Fallback to 500 + status = HttpStatus.INTERNAL_SERVER_ERROR; + } + return new ResponseEntity<>(new ErrorResponse(status, message), status); + } +} \ No newline at end of file diff --git a/src/main/java/cc/fascinated/exception/impl/BadRequestException.java b/src/main/java/cc/fascinated/exception/impl/BadRequestException.java new file mode 100644 index 0000000..9d96eb2 --- /dev/null +++ b/src/main/java/cc/fascinated/exception/impl/BadRequestException.java @@ -0,0 +1,12 @@ +package cc.fascinated.exception.impl; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.BAD_REQUEST) +public class BadRequestException extends RuntimeException { + + public BadRequestException(String message) { + super(message); + } +} diff --git a/src/main/java/cc/fascinated/exception/impl/ResourceNotFoundException.java b/src/main/java/cc/fascinated/exception/impl/ResourceNotFoundException.java new file mode 100644 index 0000000..a4f0003 --- /dev/null +++ b/src/main/java/cc/fascinated/exception/impl/ResourceNotFoundException.java @@ -0,0 +1,9 @@ +package cc.fascinated.exception.impl; + +import lombok.experimental.StandardException; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@StandardException +@ResponseStatus(HttpStatus.NOT_FOUND) +public class ResourceNotFoundException extends RuntimeException { } diff --git a/src/main/java/cc/fascinated/model/Paste.java b/src/main/java/cc/fascinated/model/Paste.java new file mode 100644 index 0000000..4ba7e5e --- /dev/null +++ b/src/main/java/cc/fascinated/model/Paste.java @@ -0,0 +1,22 @@ +package cc.fascinated.model; + +import io.micrometer.common.lang.NonNull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.data.annotation.Id; + +@AllArgsConstructor +@Getter +public class Paste { + /** + * The id of the paste. + */ + @Id @NonNull + private final String id; + + /** + * The content of the paste. + */ + @NonNull + private final String content; +} diff --git a/src/main/java/cc/fascinated/model/response/ErrorResponse.java b/src/main/java/cc/fascinated/model/response/ErrorResponse.java new file mode 100644 index 0000000..39fa0b7 --- /dev/null +++ b/src/main/java/cc/fascinated/model/response/ErrorResponse.java @@ -0,0 +1,40 @@ +package cc.fascinated.model.response; + +import io.micrometer.common.lang.NonNull; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import org.springframework.http.HttpStatus; + +import java.util.Date; + +@Getter @ToString @EqualsAndHashCode +public class ErrorResponse { + /** + * The status code of this error. + */ + @NonNull + private final HttpStatus status; + + /** + * The HTTP code of this error. + */ + private final int code; + + /** + * The message of this error. + */ + @NonNull private final String message; + + /** + * The timestamp this error occurred. + */ + @NonNull private final Date timestamp; + + public ErrorResponse(@NonNull HttpStatus status, @NonNull String message) { + this.status = status; + code = status.value(); + this.message = message; + timestamp = new Date(); + } +} \ No newline at end of file diff --git a/src/main/java/cc/fascinated/repository/PasteRepository.java b/src/main/java/cc/fascinated/repository/PasteRepository.java new file mode 100644 index 0000000..fd8afe9 --- /dev/null +++ b/src/main/java/cc/fascinated/repository/PasteRepository.java @@ -0,0 +1,9 @@ +package cc.fascinated.repository; + +import cc.fascinated.model.Paste; +import org.springframework.data.mongodb.repository.MongoRepository; + +/** + * A repository for {@link Paste}s. + */ +public interface PasteRepository extends MongoRepository { } diff --git a/src/main/java/cc/fascinated/service/PasteService.java b/src/main/java/cc/fascinated/service/PasteService.java new file mode 100644 index 0000000..b0d37ec --- /dev/null +++ b/src/main/java/cc/fascinated/service/PasteService.java @@ -0,0 +1,57 @@ +package cc.fascinated.service; + +import cc.fascinated.exception.impl.ResourceNotFoundException; +import cc.fascinated.model.Paste; +import cc.fascinated.repository.PasteRepository; +import org.apache.commons.lang3.RandomStringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +public class PasteService { + + /** + * The {@link PasteRepository} instance. + */ + private final PasteRepository pasteRepository; + + /** + * The length of the paste id. + */ + @Value("${paste.id-length}") + private int idLength; + + @Autowired + public PasteService(PasteRepository pasteRepository) { + this.pasteRepository = pasteRepository; + } + + /** + * Creates a new paste with the specified content. + * + * @param content The content of the paste. + * @return The id of the paste. + */ + public String createPaste(String content) { + return pasteRepository.save(new Paste(RandomStringUtils.randomAlphabetic(idLength), content)).getId(); + } + + /** + * Gets the content of the paste with the specified id. + * + * @param id The id of the paste. + * @return The content of the paste. + */ + public Paste getPaste(String id) { + Optional paste = pasteRepository.findById(id); + + // The paste does not exist. + if (paste.isEmpty()) { + throw new ResourceNotFoundException("Paste with id '%s' not found".formatted(id)); + } + return paste.get(); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..80652b5 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,18 @@ +server: + address: 0.0.0.0 + port: 80 + servlet: + context-path: / + +# Spring Configuration +spring: + # Don't include null properties in JSON + jackson: + default-property-inclusion: non_null + data: + + # MongoDB - This is used for general data storage + mongodb: + uri: mongodb://localhost:27017 + database: test + port: 27017 \ No newline at end of file