Skip to content

Commit

Permalink
add robots.txt, introduce first search draft
Browse files Browse the repository at this point in the history
  • Loading branch information
querwurzel committed Nov 12, 2023
1 parent 6a102e5 commit 08b72f0
Show file tree
Hide file tree
Showing 19 changed files with 259 additions and 68 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@

import static com.github.binpastes.paste.api.model.ListView.ListItemView;

@RestController
@Validated
@RestController
@RequestMapping("/api/v1/paste")
class PasteController {

Expand Down Expand Up @@ -83,7 +83,7 @@ public Mono<SearchView> 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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,40 +27,27 @@ public MySqlFullTextSupportImpl(final R2dbcEntityTemplate entityTemplate) {

@Override
public Flux<Paste> 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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public SimpleFullTextSupportImpl(final R2dbcEntityTemplate entityTemplate) {
@Override
public Flux<Paste> 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())
Expand All @@ -47,6 +47,5 @@ public Flux<Paste> searchByFullText(final String text) {
.sort(Sort.by(Sort.Direction.DESC, PasteSchema.DATE_CREATED))
)
.all();

}
}
1 change: 1 addition & 0 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="robots" content="noindex, nofollow"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="color-scheme" content="dark light">
<link rel="icon shortcut" type="image/png" href="/favicon.png"/>
</head>
<body id="root">
Expand Down
2 changes: 2 additions & 0 deletions frontend/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
User-agent: *
Disallow: /api/
2 changes: 1 addition & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ const App: () => JSX.Element = () => {
<div class={styles.leftContainer}>
<Routes>
<Route path="/" component={Create} />
<Route path="/paste/search" component={Search} />
<Route path="/paste/:id" component={Read} />
<Route path="/paste/search" component={Search} />
<Route path="*" component={NotFound} />
</Routes>
</div>
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,15 @@ const findAll = (): Promise<Array<PasteListView>> => {

const searchAll = (term: string): Promise<Array<PasteSearchView>> => {
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<void> => {
const url = new URL('/api/v1/paste/' + id, apiBaseUrl());

return fetch(url, {
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/components/CreatePaste/CreatePaste.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const CreatePaste: Component<CreatePasteProps> = ({onCreatePaste, initialPaste})
const [lastPasteUrl, setLastPasteUrl] = createSignal<string>();

let creationForm: HTMLFormElement
let submitInput: HTMLInputElement

const updateFormField = (fieldName: keyof FormModel) => (event: Event) => {
const inputElement = event.currentTarget as HTMLInputElement;
Expand Down Expand Up @@ -81,6 +82,7 @@ const CreatePaste: Component<CreatePasteProps> = ({onCreatePaste, initialPaste})
resetStore();
setLastPasteUrl(url);
})
.catch(e => submitInput.style.backgroundColor = 'red');
}

return (
Expand Down Expand Up @@ -140,8 +142,8 @@ const CreatePaste: Component<CreatePasteProps> = ({onCreatePaste, initialPaste})
<div>
<textarea minLength="5"
maxLength="4096"
required={true}
autofocus={true}
required
autofocus
rows="20"
cols="75"
placeholder="Paste here"
Expand All @@ -153,7 +155,7 @@ const CreatePaste: Component<CreatePasteProps> = ({onCreatePaste, initialPaste})
<Show when={lastPasteUrl()}>
<p class={styles.lastPaste}>{lastPasteUrl()}<Copy/></p>
</Show>
<input type="submit" value="Paste"/>
<input ref={submitInput} type="submit" value="Paste"/>
<input type="reset" value="Reset"/>
</fieldset>

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/CreatePaste/createPaste.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
.createForm input[type=submit],
.createForm input[type=reset] {
display: inline-block;
width: initial;
color: var(--color-text);
margin: .5rem;
}

Expand Down
2 changes: 2 additions & 0 deletions frontend/src/components/RecentPastes/RecentPastes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ const RecentPastes: () => JSX.Element = () => {
<span class={styles.refetch} onClick={manualRefetch}></span>
</h3>

<A class={styles.searchLink} activeClass={styles.searchLinkActive} href={'/paste/search'}>Search all pastes</A>

<ol>
<For each={pastes()}>{item =>
<li class={styles.item}>
Expand Down
10 changes: 9 additions & 1 deletion frontend/src/components/RecentPastes/recentPastes.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
list-style: none;
margin: 0;
padding: 0;

}

.recentPastes .item {
Expand Down Expand Up @@ -50,3 +49,12 @@
border-bottom: 1px dotted #a5a5a5;
}
}

.searchLink {
margin-left: 1rem;
margin-bottom: 1rem;
}

.searchLinkActive {
display: none;
}
67 changes: 59 additions & 8 deletions frontend/src/components/SearchPastes/SearchPastes.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import {createResource, createSignal, JSX} from 'solid-js';
import {Component, createResource, createSignal, JSX, Show} from 'solid-js';
import {A} from '@solidjs/router';
import ApiClient from '../../api/client';
import {PasteListView} from '../../api/model/PasteListView';
import {PasteSearchView} from '../../api/model/PasteSearchView';
import {toDateTimeString} from '../../datetime/DateTimeUtil';
import styles from "./searchPastes.module.css";

const SearchPastes: () => JSX.Element = () => {
type SearchPastesProps = {
term: String
pastes: PasteSearchView
onSearchEnter: (term: String) => void
}

const SearchPastes: Component<SearchPastesProps> = ({term, pastes, onSearchEnter}): JSX.Element => {

const [search, setSearch] = createSignal<string>();

const [results, { refetch }] = createResource(() => search(), () => searchTerm());

let searchInput: HTMLInputElement;


const searchTerm = (): Promise<Array<PasteListView>> => {
if (search() && search().length >= 3) {
return ApiClient.searchAll(search());
Expand All @@ -21,17 +33,56 @@ const SearchPastes: () => JSX.Element = () => {
refetch();
}

const submitSearchForm = (e: Event) => {

const submitOnClick = (e: Event) => {
e.preventDefault();
refetch();
if (searchInput.value?.length >= 3) {
onSearchEnter(searchInput.value);
}
}

const submitOnEnter = (e: Event) => {
e.preventDefault();

if (searchInput.value?.length >= 3) {
if (e instanceof KeyboardEvent && e.key === "Enter") {
onSearchEnter(searchInput.value);
}
}
}

return (
<div>
<>
<form autocomplete="off" class={styles.searchForm} onSubmit={submitOnClick}>
<fieldset>
<input ref={searchInput} onKeyUp={submitOnEnter} value={term} type="search" required minlength="3" maxlength="25" placeholder="Search for pastes" autofocus />
<input type="submit" value="Search"/>
<input type="reset" value="Reset"/>
</fieldset>
</form>

<Show when={term}>
<Show when={pastes.length} keyed fallback={<p>Nothing found</p>}>

<ol styles={styles.searchResults}>
<For each={pastes}>{item =>
<li class={styles.item}>
<p><A href={'/paste/' + item.id}>{item.title || 'Untitled' }</A></p>
<p>
Created: <time>{toDateTimeString(item.dateCreated)}</time> |
Expires: <time>{item.dateOfExpiry ? toDateTimeString(item.dateOfExpiry) : 'Never'}</time> |
Size: {item.sizeInBytes} bytes
</p>
<pre><i>{item.highlight} [..]”</i></pre>
</li>
}
</For>
</ol>

<h1>BinPastes</h1>
</Show>
</Show>

</div>
</>
)
}

Expand Down
61 changes: 61 additions & 0 deletions frontend/src/components/SearchPastes/searchPastes.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@

.searchForm {
border: none;
padding: 0;
}

.searchForm fieldset {
border: 1px dotted #a5a5a5;
border-radius: 7px;
margin-bottom: 10px;
padding: 5px 10px;
}

.searchForm input[type=search] {
display: inline-block;
width: 20rem;
margin: 0;
}

.searchForm input[type=submit],
.searchForm input[type=reset] {
display: inline-block;
padding: 0.4rem 0.8rem;
color: var(--color-text);
margin: .5rem;
cursor: pointer;
}




.searchResults {
margin-top: 0;
padding-top: 0;
border-left: 1px dotted #a5a5a5;
}

.searchResults ol {
list-style: none;
margin: 0;
padding: 0;

}

.searchResults .item {
list-style: none;
padding-left: 1rem;
border-top: 1px dotted #a5a5a5;
}

.searchResults .item p {
display: inline-flex;
align-items: start;
gap: 3px;
}

.searchResults .item p {
display: inline-flex;
align-items: start;
gap: 3px;
}
10 changes: 10 additions & 0 deletions frontend/src/components/Spinner/Spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {JSX} from 'solid-js';
import './spinner.css';

const Spinner: () => JSX.Element = () => {
return (
<div class="lds-ellipsis"><div></div><div></div><div></div><div></div></div>
)
}

export default Spinner;
Loading

0 comments on commit 08b72f0

Please sign in to comment.