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 5667bfe
Show file tree
Hide file tree
Showing 18 changed files with 258 additions and 67 deletions.
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 5667bfe

Please sign in to comment.