Browse Source

working on search

feature/document
Stephan Richter 4 months ago
parent
commit
b07533eaa9
  1. 4
      core/src/main/java/de/srsoftware/umbrella/core/BaseHandler.java
  2. 2
      frontend/src/App.svelte
  3. 44
      frontend/src/routes/search/Search.svelte
  4. 204
      legacy/src/main/java/de/srsoftware/umbrella/legacy/LegacyApi.java
  5. 15
      user/src/main/java/de/srsoftware/umbrella/user/UserModule.java
  6. 19
      web/src/main/java/de/srsoftware/umbrella/web/WebHandler.java

4
core/src/main/java/de/srsoftware/umbrella/core/BaseHandler.java

@ -15,7 +15,7 @@ import java.util.List; @@ -15,7 +15,7 @@ import java.util.List;
public abstract class BaseHandler extends PathHandler {
private static HttpExchange addCors(HttpExchange ex){
public HttpExchange addCors(HttpExchange ex){
var headers = ex.getRequestHeaders();
var origin = nullable(headers.get("Origin")).orElse(List.of()).stream().filter(url -> url.contains("://localhost")||url.contains("://127.0.0.1")).findAny();
if (origin.isPresent()) {
@ -25,7 +25,7 @@ public abstract class BaseHandler extends PathHandler { @@ -25,7 +25,7 @@ public abstract class BaseHandler extends PathHandler {
headers.add("Access-Control-Allow-Origin", url);
headers.add("Access-Control-Allow-Headers", "Content-Type");
headers.add("Access-Control-Allow-Credentials", "true");
headers.add("Access-Control-Allow-Methods","GET, POST, PATCH");
headers.add("Access-Control-Allow-Methods","DELETE, GET, POST, PATCH");
}
return ex;
}

2
frontend/src/App.svelte

@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
import Footer from "./Components/Footer.svelte";
import Login from "./Components/Login.svelte";
import Menu from "./Components/Menu.svelte";
import Search from "./routes/search/Search.svelte";
import User from "./routes/user/User.svelte";
import UserEdit from "./routes/user/Edit.svelte";
@ -31,6 +32,7 @@ @@ -31,6 +32,7 @@
{#if user.name }
<!-- https://github.com/notnotsamuel/svelte-tiny-router -->
<Menu />
<Route path="/search" component={Search} />
<Route path="/user" component={User} />
<Route path="/user/login" component={User} />
<Route path="/user/:user_id/edit" component={UserEdit} />

44
frontend/src/routes/search/Search.svelte

@ -0,0 +1,44 @@ @@ -0,0 +1,44 @@
<script>
import {t} from '../../translations.svelte.js';
let key = "";
let fulltext = false;
let html = "";
async function doSearch(ev){
ev.preventDefault();
const params = new URLSearchParams()
params.set('key', key);
if (fulltext) params.set('fulltext',true);
const url = `${location.protocol}//${location.host.replace('5173','8080')}/legacy/search?${params.toString()}`;
const resp = await fetch(url,{credentials:'include'});
if (resp.ok){
html = await resp.text();
if (!html) html = t('search.nothing_found');
}
}
</script>
<fieldset class="search">
<legend>{t('search.search')}</legend>
<form onsubmit={doSearch}>
<label>
{t('search.key')}
<input type="text" bind:value={key} />
</label>
<label>
<input type="checkbox" bind:checked={fulltext} />
{t('search.fulltext')}
</label>
<button type="submit">{t('search.go')}</button>
</form>
</fieldset>
{#if html}
<fieldset>
<legend>
{t('search.results')}
</legend>
{@html html}
</fieldset>
{/if}

204
legacy/src/main/java/de/srsoftware/umbrella/legacy/LegacyApi.java

@ -55,11 +55,11 @@ public class LegacyApi extends BaseHandler { @@ -55,11 +55,11 @@ public class LegacyApi extends BaseHandler {
allowOrigin(ex, "*"); // add CORS header
yield load(path,ex);
}
case LOGIN -> getLegacyLogin(ex);
case LOGIN -> getLogin(ex);
case LOGOUT-> logout(ex);
case SEARCH -> {
try {
yield legacySearch(ex);
yield search(ex);
} catch (UmbrellaException e){
yield send(ex,e);
}
@ -68,54 +68,21 @@ public class LegacyApi extends BaseHandler { @@ -68,54 +68,21 @@ public class LegacyApi extends BaseHandler {
};
}
private boolean legacySearch(HttpExchange ex) throws IOException, UmbrellaException {
var optToken = SessionToken.from(ex).map(Token::of);
if (optToken.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED,ex);
var token = optToken.get();
var params = queryParam(ex);
var key = params.get(KEY) instanceof String s ? s : null;
if (key == null) throw new UmbrellaException(HTTP_BAD_REQUEST,"Missing search key");
var fulltext = key.contains("+") || "on".equals(params.get("fulltext"));
StringBuilder searchResult = new StringBuilder();
if (fulltext){
for (var module : config.keys()){
var baseUrl = config.get(module + ".baseUrl");
if (baseUrl.isEmpty()) continue;
var res = request(baseUrl.get()+"/search",token.asMap().plus(KEY,key),MIME_FORM_URL,token.asBearer());
if (!(res instanceof String content) || content.isBlank()) continue;
searchResult.append("<fieldset><label>").append(module).append("</label>");
searchResult.append(content).append("</fieldset>\n");
}
} else {
var bookmark = config.get("bookmark.baseUrl");
if (bookmark.isEmpty()) throw new UmbrellaException(500,"Tag search not available: Bookmark module not configured!");
var res = request(bookmark.get()+"/search",token.asMap().plus(KEY,key),MIME_FORM_URL,null);
if (!(res instanceof String content)) throw new UmbrellaException(500,"Search did not return html content!");
searchResult.append(content);
}
return sendContent(ex,searchResult.toString());
@Override
public boolean doPost(Path path, HttpExchange ex) throws IOException {
try {
return switch (path.pop()) {
case JSON -> jsonUser(ex);
case NOTIFY -> legacyNotify(ex);
case VALIDATE_TOKEN -> validateToken(ex);
default -> super.doPost(path,ex);
};
private boolean logout(HttpExchange ex) throws IOException {
var returnTo = queryParam(ex).get("returnTo");
var optToken = SessionToken.from(ex).map(Token::of);
if (optToken.isPresent()) try{
var token = optToken.get();
users.dropSession(token);
var expiredToken = new SessionToken(token.toString(),"/", Instant.now().minus(1, DAYS),true);
expiredToken.addTo(ex);
if (returnTo instanceof String location) return sendRedirect(ex,location);
return sendContent(ex, Map.of(REDIRECT,"home"));
} catch (UmbrellaException e){
return send(ex,e);
}
if (returnTo instanceof String location) return sendRedirect(ex,location);
return sendContent(ex,Map.of(REDIRECT,"home"));
}
private boolean getLegacyLogin(HttpExchange ex) throws IOException {
private boolean getLogin(HttpExchange ex) throws IOException {
var returnTo = queryParam(ex).get("returnTo");
if (returnTo instanceof String url) {
var token = SessionToken.from(ex).map(SessionToken::sessionId)
@ -131,58 +98,7 @@ public class LegacyApi extends BaseHandler { @@ -131,58 +98,7 @@ public class LegacyApi extends BaseHandler {
return sendRedirect(ex,location);
}
@Override
public boolean doPost(Path path, HttpExchange ex) throws IOException {
try {
return switch (path.pop()) {
case JSON -> legacyJson(ex);
case NOTIFY -> legacyNotify(ex);
case VALIDATE_TOKEN -> validateToken(ex);
default -> super.doPost(path,ex);
};
} catch (UmbrellaException e){
return send(ex,e);
}
}
private static String stripTrailingSlash(Object o){
String url = o.toString();
if (url.endsWith("/")) return url.substring(0,url.length()-1);
return url;
}
private boolean validateToken(HttpExchange ex) throws UmbrellaException, IOException {
String body;
try {
body = body(ex);
} catch (Exception e){
throw new UmbrellaException(400,"Failed to read request body").causedBy(e);
}
var map = decode(body);
LOG.log(DEBUG,"validateToken(…, {0}), data: {1}",map);
String domain = stripTrailingSlash(map.get(DOMAIN) instanceof String s ? s : "");
var keys = config.keys();
var match = false;
for (var key : keys){
var baseUrl = config.get(key + ".baseUrl").map(LegacyApi::stripTrailingSlash).orElse(null);
if (domain.equals(baseUrl)){
match = true;
break;
}
}
if (!match) throw new UmbrellaException(500,"Failed to verify request domain!");
var o = map.get(TOKEN);
if (!(o instanceof String token)) throw new UmbrellaException(500,"Request did not contain token!");
var session = users.load(Token.of(token));
var user = users.load(session);
var userMap = user.toMap();
userMap.put(TOKEN,Map.of(TOKEN,token,EXPIRATION,session.expiration().getEpochSecond()));
return sendContent(ex,userMap);
}
private boolean legacyJson(HttpExchange ex) throws UmbrellaException, IOException {
private boolean jsonUser(HttpExchange ex) throws UmbrellaException, IOException {
Map<String, Object> data = null;
try {
data = decode(body(ex));
@ -228,12 +144,6 @@ public class LegacyApi extends BaseHandler { @@ -228,12 +144,6 @@ public class LegacyApi extends BaseHandler {
return sendContent(ex, userList.getFirst().toMap());
}
protected Session requestSession(Token token) throws UmbrellaException {
var session = users.load(token);
session = users.extend(session);
return session;
}
private boolean legacyNotify(HttpExchange ex) throws UmbrellaException, IOException {
if (messageUrl == null) throw new UmbrellaException(500,"missing configuration: umbrella.modules.message.baseUrl");
var mime = contentType(ex).orElse(null);
@ -291,4 +201,94 @@ public class LegacyApi extends BaseHandler { @@ -291,4 +201,94 @@ public class LegacyApi extends BaseHandler {
// TODO: should we return json?
return sendEmptyResponse(HTTP_OK,ex);
}
private boolean logout(HttpExchange ex) throws IOException {
var returnTo = queryParam(ex).get("returnTo");
var optToken = SessionToken.from(ex).map(Token::of);
if (optToken.isPresent()) try{
var token = optToken.get();
users.dropSession(token);
var expiredToken = new SessionToken(token.toString(),"/", Instant.now().minus(1, DAYS),true);
expiredToken.addTo(ex);
if (returnTo instanceof String location) return sendRedirect(ex,location);
return sendContent(ex, Map.of(REDIRECT,"home"));
} catch (UmbrellaException e) {
return send(ex,e);
}
if (returnTo instanceof String location) return sendRedirect(ex,location);
return sendContent(ex,Map.of(REDIRECT,"home"));
}
protected Session requestSession(Token token) throws UmbrellaException {
var session = users.load(token);
session = users.extend(session);
return session;
}
private boolean search(HttpExchange ex) throws IOException, UmbrellaException {
var optToken = SessionToken.from(ex).map(Token::of);
if (optToken.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED,ex);
var token = optToken.get();
var params = queryParam(ex);
var key = params.get(KEY) instanceof String s ? s : null;
if (key == null) throw new UmbrellaException(HTTP_BAD_REQUEST,"Missing search key");
var fulltext = key.contains("+") || "on".equals(params.get("fulltext"));
StringBuilder searchResult = new StringBuilder();
if (fulltext){
for (var module : config.keys()){
var baseUrl = config.get(module + ".baseUrl");
if (baseUrl.isEmpty()) continue;
var res = request(baseUrl.get()+"/search",token.asMap().plus(KEY,key),MIME_FORM_URL,token.asBearer());
if (!(res instanceof String content) || content.isBlank()) continue;
searchResult.append("<fieldset><label>").append(module).append("</label>");
searchResult.append(content).append("</fieldset>\n");
}
} else {
var bookmark = config.get("bookmark.baseUrl");
if (bookmark.isEmpty()) throw new UmbrellaException(500,"Tag search not available: Bookmark module not configured!");
var res = request(bookmark.get()+"/search",token.asMap().plus(KEY,key),MIME_FORM_URL,null);
if (!(res instanceof String content)) throw new UmbrellaException(500,"Search did not return html content!");
searchResult.append(content);
}
addCors(ex);
return sendContent(ex,searchResult.toString());
};
private static String stripTrailingSlash(Object o){
String url = o.toString();
if (url.endsWith("/")) return url.substring(0,url.length()-1);
return url;
}
private boolean validateToken(HttpExchange ex) throws UmbrellaException, IOException {
String body;
try {
body = body(ex);
} catch (Exception e){
throw new UmbrellaException(400,"Failed to read request body").causedBy(e);
}
var map = decode(body);
LOG.log(DEBUG,"validateToken(…, {0}), data: {1}",map);
String domain = stripTrailingSlash(map.get(DOMAIN) instanceof String s ? s : "");
var keys = config.keys();
var match = false;
for (var key : keys){
var baseUrl = config.get(key + ".baseUrl").map(LegacyApi::stripTrailingSlash).orElse(null);
if (domain.equals(baseUrl)){
match = true;
break;
}
}
if (!match) throw new UmbrellaException(500,"Failed to verify request domain!");
var o = map.get(TOKEN);
if (!(o instanceof String token)) throw new UmbrellaException(500,"Request did not contain token!");
var session = users.load(Token.of(token));
var user = users.load(session);
var userMap = user.toMap();
userMap.put(TOKEN,Map.of(TOKEN,token,EXPIRATION,session.expiration().getEpochSecond()));
return sendContent(ex,userMap);
}
}

15
user/src/main/java/de/srsoftware/umbrella/user/UserModule.java

@ -72,20 +72,7 @@ public class UserModule extends BaseHandler { @@ -72,20 +72,7 @@ public class UserModule extends BaseHandler {
logins = loginDb;
}
private HttpExchange addCors(HttpExchange ex){
var headers = ex.getRequestHeaders();
var origin = nullable(headers.get("Origin")).orElse(List.of()).stream().filter(url -> url.contains("://localhost")||url.contains("://127.0.0.1")).findAny();
if (origin.isPresent()) {
var url = origin.get();
headers = ex.getResponseHeaders();
headers.add("Allow-Origin", url);
headers.add("Access-Control-Allow-Origin", url);
headers.add("Access-Control-Allow-Headers", "Content-Type");
headers.add("Access-Control-Allow-Credentials", "true");
headers.add("Access-Control-Allow-Methods","DELETE, GET, POST, PATCH");
}
return ex;
}
private boolean deleteOIDC(HttpExchange ex, UmbrellaUser user, Path path) throws IOException {
var head = path.pop();

19
web/src/main/java/de/srsoftware/umbrella/web/WebHandler.java

@ -9,26 +9,13 @@ import static java.net.HttpURLConnection.HTTP_NOT_FOUND; @@ -9,26 +9,13 @@ import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.tools.Path;
import de.srsoftware.tools.PathHandler;
import de.srsoftware.umbrella.core.BaseHandler;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
public class WebHandler extends PathHandler {
private HttpExchange addCors(HttpExchange ex){
var headers = ex.getRequestHeaders();
var origin = nullable(headers.get("Origin")).orElse(List.of()).stream().filter(url -> url.contains("://localhost")||url.contains("://127.0.0.1")).findAny();
if (origin.isPresent()) {
var url = origin.get();
headers = ex.getResponseHeaders();
headers.add("Allow-Origin", url);
headers.add("Access-Control-Allow-Origin", url);
headers.add("Access-Control-Allow-Headers", "Content-Type");
headers.add("Access-Control-Allow-Credentials", "true");
headers.add("Access-Control-Allow-Methods","GET, POST, PATCH");
}
return ex;
}
public class WebHandler extends BaseHandler {
@Override
public boolean doGet(Path path, HttpExchange ex) throws IOException {

Loading…
Cancel
Save