Compare commits

...

42 Commits

Author SHA1 Message Date
Lee
3f4c542265 Merge pull request 'Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.10.0' (#10) from renovate/io.sentry-sentry-spring-boot-starter-jakarta-7.x into master
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m48s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 59s
Reviewed-on: #10
2024-06-09 20:07:26 +00:00
3d4dae9515 fix xml?
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 2m10s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 56s
2024-06-08 01:53:22 +01:00
88d47ef535 fix xml?
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m2s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 56s
2024-06-07 22:39:46 +01:00
da32c3bdfa remove the file extension of a paste internally so we only find by the real id
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 2m17s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m0s
2024-06-07 22:23:51 +01:00
9778a49c64 Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.10.0 2024-06-06 14:01:17 +00:00
79e0e5651a add readme 2024-06-05 12:03:10 +01:00
a7e6488e72 fix ignore content type being missing on /api/upload
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m58s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m1s
2024-06-05 11:58:47 +01:00
9c74f41022 move tailwind to be local
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m1s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 56s
2024-06-04 19:35:36 +01:00
98711948b6 fix the fix
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m2s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 56s
2024-06-04 19:31:06 +01:00
f60d5f2114 maybe order matters?
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m4s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m14s
2024-06-04 19:22:56 +01:00
c8e91ba949 add hastebin compatibility
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m45s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m14s
2024-06-04 19:16:10 +01:00
78af91e971 use a toast instead of alerting
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m1s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 59s
2024-06-02 14:24:05 +01:00
af219e69bb cleanup
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m3s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m13s
2024-06-02 13:01:46 +01:00
f3360b6041 check content type
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m2s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 57s
2024-06-02 12:40:31 +01:00
1e2503ef44 whoops, remove debug
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m2s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 55s
2024-06-02 10:44:06 +01:00
ae718e97f5 fix imports 2024-06-02 10:41:06 +01:00
2c1cccf25a block file some common headers
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m2s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m3s
2024-06-02 10:40:46 +01:00
288c127520 make the background less dark
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m11s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 56s
2024-06-02 10:25:06 +01:00
93d491445a add html compression
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m59s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 58s
2024-06-02 10:20:10 +01:00
2f008730f9 check if the paste contents is empty
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m4s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m0s
2024-06-01 22:07:17 +01:00
5b95ce2e33 add Ctrl + Enter keybind to upload paste
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m3s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 59s
2024-06-01 22:04:55 +01:00
6cd275994e fix selecting the >
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m4s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 59s
2024-06-01 22:01:19 +01:00
f046d9cc68 add time taken to paste api and fixed text size on the creation page
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m3s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m4s
2024-06-01 21:55:58 +01:00
0e585ed1cd redirect on invalid paste
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m19s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 57s
2024-06-01 21:21:55 +01:00
b2c034df5f Merge remote-tracking branch 'origin/master'
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m3s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 57s
2024-06-01 18:49:58 +01:00
c2c8b348e6 add embeds 2024-06-01 18:49:44 +01:00
Lee
09fdb97b6f Merge pull request 'Update dependency org.springframework.boot:spring-boot-starter-parent to v3.3.0' (#8) from renovate/spring-boot into master
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m47s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 57s
Reviewed-on: #8
2024-06-01 17:34:56 +00:00
Lee
acc129e9da Merge pull request 'Update maven Docker tag to v3.9.7' (#9) from renovate/maven-3.x into master
Some checks are pending
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Waiting to run
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Waiting to run
Reviewed-on: #9
2024-06-01 17:34:52 +00:00
Lee
50f370cde2 Merge pull request 'Update s4u/setup-maven-action action to v1.13.0' (#7) from renovate/s4u-setup-maven-action-1.x into master
Some checks failed
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Waiting to run
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Has been cancelled
Reviewed-on: #7
2024-06-01 17:34:40 +00:00
Lee
0888cb1ac1 Merge pull request 'Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.9.0' (#6) from renovate/io.sentry-sentry-spring-boot-starter-jakarta-7.x into master
Some checks are pending
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Waiting to run
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Waiting to run
Reviewed-on: #6
2024-06-01 17:34:31 +00:00
Lee
fe13824d04 Merge pull request 'Update dependency de.flapdoodle.embed:de.flapdoodle.embed.mongo.spring3x to v4.13.0' (#5) from renovate/de.flapdoodle.embed-de.flapdoodle.embed.mongo.spring3x-4.x into master
Some checks are pending
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Waiting to run
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Waiting to run
Reviewed-on: #5
2024-06-01 17:34:20 +00:00
1ab282772e re-enable the cache and make the buttons smaller
Some checks failed
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Waiting to run
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Has been cancelled
2024-06-01 18:33:40 +01:00
779460689a fix text size for the code block
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m0s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 58s
2024-06-01 18:27:58 +01:00
4a2d3d4fb7 custom select all only for the code
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m2s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 57s
2024-06-01 18:25:46 +01:00
3c4f5089ef fix test
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m19s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 58s
2024-06-01 18:18:41 +01:00
4c4fcc884d move to backend as the frontend (easier to manage and deploy)
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Failing after 1m5s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Failing after 28s
2024-06-01 18:16:54 +01:00
d6a6b45ec7 Update maven Docker tag to v3.9.7 2024-05-28 21:01:17 +00:00
7e678be372 Update dependency org.springframework.boot:spring-boot-starter-parent to v3.3.0 2024-05-23 14:01:20 +00:00
64f33e1c3c Update s4u/setup-maven-action action to v1.13.0 2024-05-17 19:01:14 +00:00
93192d6b5f Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.9.0 2024-05-08 14:01:13 +00:00
0150be17e7 Update dependency de.flapdoodle.embed:de.flapdoodle.embed.mongo.spring3x to v4.13.0 2024-04-30 22:01:07 +00:00
f2783d14da add Sentry
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m53s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m7s
2024-04-27 08:45:57 +01:00
20 changed files with 602 additions and 26 deletions

View File

@ -27,7 +27,7 @@ jobs:
# Setup Java and Maven
- name: Set up JDK and Maven
uses: s4u/setup-maven-action@v1.12.0
uses: s4u/setup-maven-action@v1.13.0
with:
java-version: ${{ matrix.java-version }}
distribution: "zulu"

View File

@ -26,7 +26,7 @@ jobs:
# Setup Java and Maven
- name: Set up JDK and Maven
uses: s4u/setup-maven-action@v1.12.0
uses: s4u/setup-maven-action@v1.13.0
with:
java-version: ${{ matrix.java-version }}
distribution: "zulu"

View File

@ -1,5 +1,5 @@
# Stage 1: Build the application
FROM maven:3.9.6-eclipse-temurin-17-alpine AS builder
FROM maven:3.9.7-eclipse-temurin-17-alpine AS builder
# Set the working directory
WORKDIR /home/container

32
README.md Normal file
View File

@ -0,0 +1,32 @@
# Paste
A simple pastebin service. Running at [paste.fascinated.cc](https://paste.fascinated.cc).
## Javascript Utility
```js
/**
* Uploads a paste to paste.fascinated.cc
*
* @param content the content of the paste
* @returns the paste key and the URL
*/
async function uploadPaste(content: string) {
const response = await fetch("https://paste.fascinated.cc/api/upload", {
method: "POST",
body: content,
});
const json = await response.json();
if (!response.ok) {
throw new Error(json.message);
}
return {
...json,
url: `https://paste.fascinated.cc/${json.key}`,
};
}
console.log(await uploadPaste("Hello, World!"));
```

22
pom.xml
View File

@ -17,7 +17,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<version>3.3.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
@ -52,6 +52,11 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>3.3.0</version>
</dependency>
<!-- MongoDB for data storage -->
<dependency>
@ -72,6 +77,19 @@
<version>3.14.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.googlecode.htmlcompressor</groupId>
<artifactId>htmlcompressor</artifactId>
<version>1.5.2</version>
</dependency>
<!-- Sentry -->
<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry-spring-boot-starter-jakarta</artifactId>
<version>7.10.0</version>
<scope>compile</scope>
</dependency>
<!-- Tests -->
<dependency>
@ -82,7 +100,7 @@
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo.spring3x</artifactId>
<version>4.12.6</version>
<version>4.13.0</version>
<scope>test</scope>
</dependency>
</dependencies>

View File

@ -0,0 +1,76 @@
package cc.fascinated.backend.common;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* @author Fascinated (fascinated7)
*/
public class FileHeaderChecker {
// Define known file headers
private static final Map<String, byte[]> FILE_HEADERS = new HashMap<>();
static {
FILE_HEADERS.put("PNG", new byte[]{(byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A});
FILE_HEADERS.put("JPEG", new byte[]{(byte) 0xFF, (byte) 0xD8, (byte) 0xFF});
FILE_HEADERS.put("GIF", new byte[]{0x47, 0x49, 0x46, 0x38});
FILE_HEADERS.put("PDF", new byte[]{0x25, 0x50, 0x44, 0x46});
FILE_HEADERS.put("ZIP", new byte[]{0x50, 0x4B, 0x03, 0x04});
FILE_HEADERS.put("RAR", new byte[]{0x52, 0x61, 0x72, 0x21});
FILE_HEADERS.put("7Z", new byte[]{0x37, 0x7A, (byte) 0xBC, (byte) 0xAF, (byte) 0x27, 0x1C});
FILE_HEADERS.put("TAR", new byte[]{0x75, 0x73, 0x74, 0x61, 0x72});
FILE_HEADERS.put("BZ2", new byte[]{0x42, 0x5A, 0x68});
FILE_HEADERS.put("GZ", new byte[]{0x1F, (byte) 0x8B});
FILE_HEADERS.put("EXE", new byte[]{0x4D, 0x5A});
}
/**
* Converts a string to a byte array using UTF-8 encoding.
*
* @param input the string to convert
* @return the byte array representation of the string
*/
private static byte[] stringToByteArray(String input) {
return input.getBytes(StandardCharsets.UTF_8);
}
/**
* Checks if the byte array starts with the specified header.
*
* @param byteArray the byte array to check
* @param header the header to look for
* @return true if the byte array starts with the header, false otherwise
*/
private static boolean startsWith(byte[] byteArray, byte[] header) {
if (byteArray.length < header.length) {
return false;
}
for (int i = 0; i < header.length; i++) {
if (byteArray[i] != header[i]) {
return false;
}
}
return true;
}
/**
* Checks if the given string contains a known file header.
*
* @param input the string to check
* @return true if the string contains a known file header, false otherwise
*/
public static boolean containsFileHeader(String input) {
byte[] byteArray = stringToByteArray(input);
for (byte[] header : FILE_HEADERS.values()) {
if (startsWith(byteArray, header)) {
return true;
}
}
return false;
}
}

View File

@ -4,10 +4,18 @@ import lombok.NonNull;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class Config {
@Configuration @EnableWebMvc
public class Config implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
}
@Bean
public WebMvcConfigurer configureCors() {

View File

@ -0,0 +1,40 @@
package cc.fascinated.backend.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.thymeleaf.spring6.SpringTemplateEngine;
import org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring6.view.ThymeleafViewResolver;
/**
* @author Fascinated (fascinated7)
*/
@Configuration
@EnableWebMvc
public class ThymeleafConfiguration {
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(thymeleafTemplateResolver());
return templateEngine;
}
@Bean
public SpringResourceTemplateResolver thymeleafTemplateResolver() {
SpringResourceTemplateResolver templateResolver
= new SpringResourceTemplateResolver();
templateResolver.setPrefix("classpath:/templates/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("HTML");
return templateResolver;
}
@Bean
public ThymeleafViewResolver thymeleafViewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
return viewResolver;
}
}

View File

@ -0,0 +1,70 @@
package cc.fascinated.backend.controller;
import cc.fascinated.backend.exception.impl.ResourceNotFoundException;
import cc.fascinated.backend.model.Paste;
import cc.fascinated.backend.service.PasteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.bind.annotation.*;
import java.awt.datatransfer.MimeTypeParseException;
import java.util.Map;
/**
* @author Fascinated (fascinated7)
*/
@Controller
@RequestMapping(value = "/")
public class IndexController {
private final PasteService pasteService;
@Autowired
public IndexController(PasteService pasteService) {
this.pasteService = pasteService;
}
@GetMapping(value = "/")
public String home() {
return "index";
}
/**
* This is to allow for Hastebin compatibility.
*/
@PostMapping(value = "/documents")
public ResponseEntity<?> uploadPaste(@RequestBody String content, @RequestHeader(value = HttpHeaders.CONTENT_TYPE, required = false) String contentType) {
String id = pasteService.createPaste(content, contentType);
return ResponseEntity.ok(Map.of("key", id));
}
@GetMapping(value = "/{id}", produces = MimeTypeUtils.TEXT_HTML_VALUE)
public String paste(@PathVariable String id, Model model) {
try {
Paste paste = pasteService.getPaste(id);
model.addAttribute("paste", paste);
model.addAttribute("title", "Paste - " + paste.getId());
model.addAttribute("rawUrl", "/raw/" + paste.getId());
return "paste";
} catch (ResourceNotFoundException ex) {
return "redirect:/";
}
}
@GetMapping(value = "/raw/{id}", produces = MimeTypeUtils.TEXT_HTML_VALUE)
public String pasteRaw(@PathVariable String id, Model model) {
try {
Paste paste = pasteService.getPaste(id);
model.addAttribute("paste", paste);
model.addAttribute("title", "Paste - " + paste.getId() + " (Raw)");
return "paste-raw";
} catch (ResourceNotFoundException ex) {
return "redirect:/";
}
}
}

View File

@ -3,14 +3,14 @@ package cc.fascinated.backend.controller;
import cc.fascinated.backend.model.Paste;
import cc.fascinated.backend.service.PasteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping(value = "/")
@RequestMapping(value = "/api")
public class PasteController {
private final PasteService pasteService;
@ -20,20 +20,13 @@ public class PasteController {
this.pasteService = pasteService;
}
@GetMapping(value = "/")
public ResponseEntity<?> home() {
return ResponseEntity.ok(Map.of(
"status", "OK"
));
@PostMapping(value = "/upload")
public ResponseEntity<?> uploadPaste(@RequestBody String content, @RequestHeader(value = HttpHeaders.CONTENT_TYPE, required = false) String contentType) {
String id = pasteService.createPaste(content, contentType);
return ResponseEntity.ok(Map.of("key", id));
}
@PostMapping(value = "/upload", consumes = MediaType.TEXT_PLAIN_VALUE)
public ResponseEntity<?> uploadPaste(@RequestBody String content) {
String id = pasteService.createPaste(content);
return ResponseEntity.ok(Map.of("id", id));
}
@GetMapping(value = "/{id}")
@GetMapping(value = "/paste/{id}")
public ResponseEntity<Paste> getPaste(@PathVariable String id) {
Paste paste = pasteService.getPaste(id);
return ResponseEntity.ok(paste);

View File

@ -2,6 +2,7 @@ package cc.fascinated.backend.exception;
import cc.fascinated.backend.model.response.ErrorResponse;
import io.micrometer.common.lang.NonNull;
import io.sentry.Sentry;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
@ -39,6 +40,7 @@ public final class ExceptionControllerAdvice {
}
if (status == null) { // Fallback to 500
status = HttpStatus.INTERNAL_SERVER_ERROR;
Sentry.captureException(ex); // Capture the exception
}
return new ResponseEntity<>(new ErrorResponse(status, message), status);
}

View File

@ -0,0 +1,98 @@
package cc.fascinated.backend.filter;
import com.googlecode.htmlcompressor.compressor.HtmlCompressor;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponseWrapper;
import org.springframework.stereotype.Component;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.PrintWriter;
/**
* Filter to compress HTML responses using HtmlCompressor.
* Author: Fascinated (fascinated7)
*/
@Component
public class HtmlCompressionFilter implements Filter {
private final HtmlCompressor htmlCompressor = new HtmlCompressor();
/**
* Applies the HTML compression filter.
*
* @param request the ServletRequest
* @param response the ServletResponse
* @param chain the FilterChain
* @throws ServletException if an error occurs during filtering
* @throws IOException if an input or output error occurs
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws ServletException, IOException {
ServletResponse newResponse = response;
// Wrap the response to capture the output
if (request instanceof HttpServletRequest) {
newResponse = new CharResponseWrapper((HttpServletResponse) response);
}
// Pass the request and response along the filter chain
chain.doFilter(request, newResponse);
// Compress the captured response if it was wrapped
if (newResponse instanceof CharResponseWrapper) {
String responseContent = newResponse.toString();
if (responseContent != null) {
response.getWriter().write(htmlCompressor.compress(responseContent));
}
}
}
/**
* Wrapper for HttpServletResponse to capture the output for compression.
*/
private static class CharResponseWrapper extends HttpServletResponseWrapper {
private final CharArrayWriter charWriter = new CharArrayWriter();
private PrintWriter writer;
private boolean getOutputStreamCalled;
private boolean getWriterCalled;
/**
* Constructor to wrap the response.
* @param response the original HttpServletResponse
*/
public CharResponseWrapper(HttpServletResponse response) {
super(response);
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
if (getWriterCalled) {
throw new IllegalStateException("getWriter already called");
}
getOutputStreamCalled = true;
return super.getOutputStream();
}
@Override
public PrintWriter getWriter() {
if (writer != null) {
return writer;
}
if (getOutputStreamCalled) {
throw new IllegalStateException("getOutputStream already called");
}
getWriterCalled = true;
writer = new PrintWriter(charWriter);
return writer;
}
@Override
public String toString() {
return (writer != null) ? charWriter.toString() : null;
}
}
}

View File

@ -1,5 +1,6 @@
package cc.fascinated.backend.service;
import cc.fascinated.backend.common.FileHeaderChecker;
import cc.fascinated.backend.exception.impl.BadRequestException;
import cc.fascinated.backend.exception.impl.ResourceNotFoundException;
import cc.fascinated.backend.model.Paste;
@ -43,14 +44,27 @@ public class PasteService {
* @param content The content of the paste.
* @return The id of the paste.
*/
public String createPaste(String content) {
public String createPaste(String content, String contentType) {
int length = content.length();
long before = System.currentTimeMillis();
log.info("Creating a new paste. (characters: {})", length);
// Check if the content is too large.
if (length > uploadSizeLimit && uploadSizeLimit != -1) {
log.info("Paste didn't meet the size requirements. (characters: {})", length);
throw new BadRequestException("The paste content is too large, the limit is " + uploadSizeLimit + " characters");
throw new BadRequestException("The paste content is too large, the limit is " + uploadSizeLimit + " characters, not uploading...");
}
// Ensure the paste content type is valid.
if (contentType != null && (contentType.contains("image") || contentType.contains("video") || contentType.contains("audio"))) {
log.info("Paste content type is not supported. (content type: {})", contentType);
throw new BadRequestException("The paste content type is not supported, not uploading...");
}
// Ensure the paste content does not contain a file header.
if (FileHeaderChecker.containsFileHeader(content)) {
log.info("Paste content contains a file header, not uploading...");
throw new BadRequestException("The paste content contains a file header, not uploading...");
}
// Save the paste to the database.
@ -59,7 +73,7 @@ public class PasteService {
System.currentTimeMillis(),
content
));
log.info("Created a new paste with the id '{}'", paste.getId());
log.info("Created a new paste with the id '{}' (took: {}ms)", paste.getId(), System.currentTimeMillis() - before);
return paste.getId();
}
@ -70,7 +84,9 @@ public class PasteService {
* @return The content of the paste.
*/
public Paste getPaste(String id) {
id = id.contains(".") ? id.split("\\.")[0] : id; // Remove file extensions (if any)
log.info("Getting paste with the id '{}'", id);
long before = System.currentTimeMillis();
Optional<Paste> paste = pasteRepository.findById(id);
// The paste does not exist.
@ -78,7 +94,7 @@ public class PasteService {
log.info("Paste with the id '{}' not found", id);
throw new ResourceNotFoundException("Paste '%s' not found".formatted(id));
}
log.info("Got paste with the id '{}'", id);
log.info("Got paste with the id '{}' (took: {}ms)", id, System.currentTimeMillis() - before);
return paste.get();
}
}

View File

@ -19,6 +19,11 @@ spring:
database: test
port: 27017
# Sentry Configuration
sentry:
dsn: ""
tracesSampleRate: 1.0
# Paste Configuration
paste:
# The length of the ID for the paste

View File

@ -0,0 +1,49 @@
// Handle custom key binds behavior
document.addEventListener("keydown", function (event) {
// Upload the paste when Ctrl + Enter is pressed
if (event.ctrlKey && event.key === "Enter") {
event.preventDefault();
upload();
}
});
// Upload the paste when the paste button is clicked
document.getElementById("paste-button").addEventListener("click", () => upload());
// Upload the paste to the server
const upload = async () => {
var pasteInput = document.getElementById("paste-input");
var paste = pasteInput.value;
if (!paste || paste.trim() === "") {
pasteInput.focus();
toast("Please enter a paste to upload.");
return;
}
console.log("Uploading paste...");
try {
const response = await fetch("/api/upload", {
method: "POST",
body: paste,
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message);
}
window.location.href = "/" + data.key;
} catch (error) {
console.error("Error:", error);
toast(`${error.message || "An error occurred while uploading the paste."}`);
}
};
const toast = (message, duration = 3000) => {
Toastify({
text: message,
duration: duration,
gravity: "bottom",
position: "right"
}).showToast();
};

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,37 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Paste</title>
<!-- TailwindCSS -->
<script src="/static/tailwindcss.js"></script>
<!-- Toastify -->
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/toastify-js"></script>
</head>
<body class="bg-neutral-900 text-white">
<div class="p-3 h-screen w-screen relative">
<div class="flex gap-2 h-full w-full text-sm">
<p class="hidden md:block select-none pointer-events-none">&gt;</p>
<textarea
id="paste-input"
class="w-full h-full bg-background outline-none resize-none bg-transparent"
placeholder="Paste your code here..."></textarea>
</div>
<div class="absolute top-0 right-0 p-3">
<button id="paste-button" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 rounded">
Paste
</button>
</div>
</div>
</body>
<!-- Paste Script -->
<script src="/static/assets/script.js"></script>
</html>

View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title th:text="${title}"></title>
</head>
<body>
<pre><code th:text="${paste.content}"></code></pre>
</body>
</html>

View File

@ -0,0 +1,57 @@
<!doctype html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title th:text="${title}"></title>
<!-- Meta tags for social media sharing -->
<meta th:content="${title}" property="og:title" />
<meta content="Click to view the Paste." property="og:description" />
<!-- TailwindCSS -->
<script src="/static/tailwindcss.js"></script>
<!-- Highlight.js -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
</head>
<body class="bg-neutral-900 text-white h-screen relative">
<pre><code class="bg-transparent text-sm" th:text="${paste.content}"></code></pre>
<div class="absolute top-0 right-0 p-3">
<button id="raw-button" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 rounded">
<a th:href="${rawUrl}">
Raw
</a>
</button>
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 rounded">
<a href="/">
New
</a>
</button>
</div>
</body>
<script>
// Highlight the code block
hljs.highlightAll();
// Custom select all behavior to select all the content inside the <pre> block
document.addEventListener('keydown', function(event) {
if (event.ctrlKey && event.key === 'a') {
event.preventDefault(); // Prevent the default select all behavior
// Select all the content inside the <pre> block
const preBlock = document.querySelector('pre');
const range = document.createRange();
range.selectNodeContents(preBlock);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
});
</script>
</html>

View File

@ -20,7 +20,7 @@ class PasteControllerTests {
@Test
public void ensureUploadSuccess() throws Exception {
ResultActions result = mockMvc.perform(post("/upload")
ResultActions result = mockMvc.perform(post("/api/upload")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.TEXT_PLAIN)
.content("joe"))