diff --git a/README.md b/README.md index c0291c5..c6126ab 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Apache License 2.0 ([Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0)) * JDK 17+ * MySQL 8+ -Just put the [JDK](https://adoptium.net/temurin/releases/) somewhere on your file system. +Just put the [JDK](https://adoptium.net/en-GB/temurin/releases/?os=any&arch=any&package=jdk&version=17) somewhere on your file system. The `bin` folder contains the `java` binary. ### How to configure diff --git a/backend/src/main/java/com/github/binpastes/config/WebServerConfig.java b/backend/src/main/java/com/github/binpastes/config/WebServerConfig.java index 0c13fef..f19fe2d 100644 --- a/backend/src/main/java/com/github/binpastes/config/WebServerConfig.java +++ b/backend/src/main/java/com/github/binpastes/config/WebServerConfig.java @@ -49,6 +49,8 @@ public RouterFunctionMapping indexRoute(@Value("static/index.html") final ClassP var route = route(RequestPredicates .method(HttpMethod.GET) + .and(path("/robots.txt").negate()) + .and(path("/favicon.png").negate()) .and(path("/assets/**").negate()) .and(path("/api/**").negate()), request -> ok().contentType(MediaType.TEXT_HTML).bodyValue(indexHtml)); diff --git a/backend/src/main/java/com/github/binpastes/paste/api/PasteController.java b/backend/src/main/java/com/github/binpastes/paste/api/PasteController.java index 62c30ef..b70aa56 100644 --- a/backend/src/main/java/com/github/binpastes/paste/api/PasteController.java +++ b/backend/src/main/java/com/github/binpastes/paste/api/PasteController.java @@ -29,8 +29,8 @@ import static com.github.binpastes.paste.api.model.ListView.ListItemView; -@RestController @Validated +@RestController @RequestMapping("/api/v1/paste") class PasteController { @@ -83,7 +83,7 @@ public Mono searchPastes( final String term, final ServerHttpResponse response ) { - response.getHeaders().add(HttpHeaders.CACHE_CONTROL, "max-age=300"); + response.getHeaders().add(HttpHeaders.CACHE_CONTROL, "max-age=60"); return pasteService .findByFullText(term) .map(paste -> SearchItemView.of(paste, term)) diff --git a/backend/src/main/java/com/github/binpastes/paste/api/model/SearchView.java b/backend/src/main/java/com/github/binpastes/paste/api/model/SearchView.java index ef3a296..11d7c7d 100644 --- a/backend/src/main/java/com/github/binpastes/paste/api/model/SearchView.java +++ b/backend/src/main/java/com/github/binpastes/paste/api/model/SearchView.java @@ -22,7 +22,7 @@ public record SearchItemView( LocalDateTime dateOfExpiry ) { - private static final short HIGHLIGHT_RANGE = 25; + private static final short HIGHLIGHT_RANGE = 30; public static SearchItemView of(final Paste reference, final String term) { return new SearchItemView( diff --git a/backend/src/main/java/com/github/binpastes/paste/domain/MySqlFullTextSupportImpl.java b/backend/src/main/java/com/github/binpastes/paste/domain/MySqlFullTextSupportImpl.java index 3c05f0e..bfe1c77 100644 --- a/backend/src/main/java/com/github/binpastes/paste/domain/MySqlFullTextSupportImpl.java +++ b/backend/src/main/java/com/github/binpastes/paste/domain/MySqlFullTextSupportImpl.java @@ -27,40 +27,27 @@ public MySqlFullTextSupportImpl(final R2dbcEntityTemplate entityTemplate) { @Override public Flux searchByFullText(final String text) { - /* - * Seems not to be supported by dev.miku:r2dbc-mysql - * java.lang.IllegalArgumentException: Cannot encode value of type 'class io.r2dbc.spi.Parameters$InParameter' - **/ -/* - entityTemplate - .getDatabaseClient() - .sql("SELECT * FROM pastes WHERE (date_of_expiry IS NULL OR date_of_expiry > CURRENT_TIMESTAMP) AND MATCH(title, content) AGAINST(?text IN BOOLEAN MODE)") - .bind("text", text + '*')) -*/ - - var connectionFactory = entityTemplate.getDatabaseClient().getConnectionFactory(); - - var query = String.format("SELECT * FROM %s WHERE %s = ? AND (%s IS NULL OR %s > ?) AND (MATCH(%s) AGAINST(? IN BOOLEAN MODE) OR (MATCH(%s) AGAINST(? IN BOOLEAN MODE) AND %s IS FALSE)) ORDER BY %s DESC", + var query = String.format("SELECT * FROM %s WHERE %s = ? AND (%s IS NULL OR %s > ?) AND (MATCH(%s) AGAINST(?) OR (%s IS FALSE AND MATCH(%s) AGAINST(?)))", PasteSchema.TABLE_NAME, PasteSchema.EXPOSURE, PasteSchema.DATE_OF_EXPIRY, PasteSchema.DATE_OF_EXPIRY, PasteSchema.TITLE, - PasteSchema.CONTENT, PasteSchema.IS_ENCRYPTED, - PasteSchema.DATE_CREATED + PasteSchema.CONTENT ); + var connectionFactory = entityTemplate.getDatabaseClient().getConnectionFactory(); return Mono.from(connectionFactory.create()) .flatMap(mySqlConnection -> Mono.from(mySqlConnection - .createStatement(query) - .bind(0, PasteExposure.PUBLIC.name()) - .bind(1, LocalDateTime.now()) - .bind(2, text + '*') - .bind(3, text + '*') - .execute() + .createStatement(query) + .bind(0, PasteExposure.PUBLIC) + .bind(1, LocalDateTime.now()) + .bind(2, text) + .bind(3, text) + .execute() )) - .flatMapMany(mySqlResult -> Flux.from(mySqlResult.map((row, rowMetadata) -> { + .flatMapMany(mySqlResult -> Flux.from(mySqlResult.map((row) -> { var paste = new Paste(); paste.setId(row.get(PasteSchema.ID, String.class)); paste.setVersion(row.get(PasteSchema.VERSION, Long.class)); diff --git a/backend/src/main/java/com/github/binpastes/paste/domain/SimpleFullTextSupportImpl.java b/backend/src/main/java/com/github/binpastes/paste/domain/SimpleFullTextSupportImpl.java index 0e93b05..4b616da 100644 --- a/backend/src/main/java/com/github/binpastes/paste/domain/SimpleFullTextSupportImpl.java +++ b/backend/src/main/java/com/github/binpastes/paste/domain/SimpleFullTextSupportImpl.java @@ -26,7 +26,7 @@ public SimpleFullTextSupportImpl(final R2dbcEntityTemplate entityTemplate) { @Override public Flux searchByFullText(final String text) { var criteria = Criteria - .where(PasteSchema.EXPOSURE).is(PasteExposure.PUBLIC.name()) + .where(PasteSchema.EXPOSURE).is(PasteExposure.PUBLIC) .and(Criteria .where(PasteSchema.DATE_OF_EXPIRY).isNull() .or(PasteSchema.DATE_OF_EXPIRY).greaterThan(LocalDateTime.now()) @@ -47,6 +47,5 @@ public Flux searchByFullText(final String text) { .sort(Sort.by(Sort.Direction.DESC, PasteSchema.DATE_CREATED)) ) .all(); - } } diff --git a/frontend/index.html b/frontend/index.html index 713ddcc..b69f8c0 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -8,6 +8,7 @@ + diff --git a/frontend/public/robots.txt b/frontend/public/robots.txt new file mode 100644 index 0000000..d346249 --- /dev/null +++ b/frontend/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: /api/ diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 9d3fc62..624d242 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -23,8 +23,8 @@ const App: () => JSX.Element = () => {
- +
diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 94ba078..e0839d8 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -56,14 +56,15 @@ const findAll = (): Promise> => { const searchAll = (term: string): Promise> => { const params = new URLSearchParams([['term', term]]); - const url = new URL('/api/v1/paste/search?' + params.toString(), apiBaseUrl()); + const url = new URL('/api/v1/paste/search?' + encodeURI(params.toString()), apiBaseUrl()); return fetch(url) .then(value => value.json()) - .then(value => value.pastes); + .then(value => value.pastes) + .catch(_ => []) } -const deletePaste = (id: string) => { +const deletePaste = (id: string): Promise => { const url = new URL('/api/v1/paste/' + id, apiBaseUrl()); return fetch(url, { diff --git a/frontend/src/components/CreatePaste/CreatePaste.tsx b/frontend/src/components/CreatePaste/CreatePaste.tsx index a238e76..805a59e 100644 --- a/frontend/src/components/CreatePaste/CreatePaste.tsx +++ b/frontend/src/components/CreatePaste/CreatePaste.tsx @@ -36,6 +36,7 @@ const CreatePaste: Component = ({onCreatePaste, initialPaste}) const [lastPasteUrl, setLastPasteUrl] = createSignal(); let creationForm: HTMLFormElement + let submitInput: HTMLInputElement const updateFormField = (fieldName: keyof FormModel) => (event: Event) => { const inputElement = event.currentTarget as HTMLInputElement; @@ -81,6 +82,7 @@ const CreatePaste: Component = ({onCreatePaste, initialPaste}) resetStore(); setLastPasteUrl(url); }) + .catch(e => submitInput.style.backgroundColor = 'red'); } return ( @@ -140,8 +142,8 @@ const CreatePaste: Component = ({onCreatePaste, initialPaste})