From 2d6b017352267de7f4c746d004521203be81801d Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Fri, 13 Mar 2026 14:50:12 +0100 Subject: [PATCH] working on bookmark editing Signed-off-by: Stephan Richter --- bookmark/build.gradle.kts | 1 + .../umbrella/bookmarks/BookmarkApi.java | 94 ++++++++++++++++--- .../umbrella/bookmarks/BookmarkDb.java | 4 + .../umbrella/bookmarks/SqliteDb.java | 30 +++++- .../messagebus/events/BookmarkEvent.java | 41 ++++++++ .../umbrella/core/api/TagService.java | 4 +- frontend/src/routes/bookmark/Index.svelte | 35 ++++++- frontend/src/routes/bookmark/Template.svelte | 24 ++++- frontend/src/routes/bookmark/View.svelte | 55 ++++++++++- frontend/src/routes/task/Index.svelte | 2 +- .../de/srsoftware/umbrella/tags/SqliteDb.java | 12 ++- .../de/srsoftware/umbrella/tags/TagDB.java | 2 + .../srsoftware/umbrella/tags/TagModule.java | 5 + 13 files changed, 283 insertions(+), 26 deletions(-) create mode 100644 bus/src/main/java/de/srsoftware/umbrella/messagebus/events/BookmarkEvent.java diff --git a/bookmark/build.gradle.kts b/bookmark/build.gradle.kts index 4eebb359..6bd54eb8 100644 --- a/bookmark/build.gradle.kts +++ b/bookmark/build.gradle.kts @@ -1,6 +1,7 @@ description = "Umbrella : Bookmarks" dependencies{ + implementation(project(":bus")) implementation(project(":core")) } diff --git a/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/BookmarkApi.java b/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/BookmarkApi.java index 2bc1f5db..700701bb 100644 --- a/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/BookmarkApi.java +++ b/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/BookmarkApi.java @@ -1,8 +1,10 @@ /* © SRSoftware 2025 */ package de.srsoftware.umbrella.bookmarks; +import static de.srsoftware.umbrella.messagebus.events.Event.EventType; import static de.srsoftware.umbrella.bookmarks.Constants.*; import static de.srsoftware.umbrella.core.ConnectionProvider.connect; +import static de.srsoftware.umbrella.core.ModuleRegistry.tagService; import static de.srsoftware.umbrella.core.Util.mapValues; import static de.srsoftware.umbrella.core.constants.Field.*; import static de.srsoftware.umbrella.core.constants.Field.TAGS; @@ -10,7 +12,11 @@ import static de.srsoftware.umbrella.core.constants.Module.BOOKMARK; import static de.srsoftware.umbrella.core.constants.Path.LIST; import static de.srsoftware.umbrella.core.constants.Path.SEARCH; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*; +import static de.srsoftware.umbrella.messagebus.MessageBus.messageBus; +import static de.srsoftware.umbrella.messagebus.events.Event.EventType.CREATE; +import static de.srsoftware.umbrella.messagebus.events.Event.EventType.UPDATE; import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; +import static java.net.HttpURLConnection.HTTP_OK; import com.sun.net.httpserver.HttpExchange; import de.srsoftware.configuration.Configuration; @@ -19,14 +25,18 @@ import de.srsoftware.tools.SessionToken; import de.srsoftware.umbrella.core.BaseHandler; import de.srsoftware.umbrella.core.ModuleRegistry; import de.srsoftware.umbrella.core.api.BookmarkService; +import de.srsoftware.umbrella.core.constants.Text; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.model.Token; import de.srsoftware.umbrella.core.model.UmbrellaUser; + +import java.awt.print.Book; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Map; -import java.util.Optional; +import java.util.*; + +import de.srsoftware.umbrella.messagebus.events.BookmarkEvent; +import de.srsoftware.umbrella.messagebus.events.Event; +import de.srsoftware.umbrella.messagebus.events.TaskEvent; import org.json.JSONArray; public class BookmarkApi extends BaseHandler implements BookmarkService { @@ -39,6 +49,30 @@ public class BookmarkApi extends BaseHandler implements BookmarkService { ModuleRegistry.add(this); } + private boolean deleteBookmark(UmbrellaUser user, HttpExchange ex, long urlId) throws IOException { + var bookmark = db.load(urlId,user.id()); + db.remove(user, bookmark); + return sendEmptyResponse(HTTP_OK,ex); + } + + @Override + public boolean doDelete(Path path, HttpExchange ex) throws IOException { + addCors(ex); + try { + Optional token = SessionToken.from(ex).map(Token::of); + var user = ModuleRegistry.userService().loadUser(token); + if (user.isEmpty()) return unauthorized(ex); + var head = path.pop(); + if (head == null) throw missingField(ID); + var id = Long.parseLong(head); + return deleteBookmark(user.get(),ex,id); + } catch (NumberFormatException e){ + throw invalidField(ID, Text.NUMBER); + } catch (UmbrellaException e){ + return send(ex,e); + } + } + @Override public boolean doGet(Path path, HttpExchange ex) throws IOException { addCors(ex); @@ -51,21 +85,33 @@ public class BookmarkApi extends BaseHandler implements BookmarkService { case LIST -> getUserBookmarks(user.get(),ex); case null -> super.doPost(path,ex); default -> { - var id = Long.parseLong(head); - yield getBookmark(user.get(),id,ex); + var urlId = Long.parseLong(head); + yield getBookmark(user.get(),urlId,ex); } }; } catch (NumberFormatException e){ - return sendContent(ex,HTTP_BAD_REQUEST,"Invalid project id"); + throw invalidField(ID, Text.NUMBER); } catch (UmbrellaException e){ return send(ex,e); } } - private boolean getBookmark(UmbrellaUser user, long id, HttpExchange ex) throws IOException { - var bookmark = db.load(id,user.id()); - ModuleRegistry.tagService().getTags(BOOKMARK, id, user).forEach(bookmark.tags()::add); - return sendContent(ex,bookmark); + @Override + public boolean doPatch(Path path, HttpExchange ex) throws IOException { + addCors(ex); + try { + Optional token = SessionToken.from(ex).map(Token::of); + var user = ModuleRegistry.userService().loadUser(token); + if (user.isEmpty()) return unauthorized(ex); + var head = path.pop(); + if (head == null) throw missingField(ID); + var id = Long.parseLong(head); + return patchBookmark(user.get(),ex,id); + } catch (NumberFormatException e){ + throw invalidField(ID, Text.NUMBER); + } catch (UmbrellaException e){ + return send(ex,e); + } } @Override @@ -93,6 +139,12 @@ public class BookmarkApi extends BaseHandler implements BookmarkService { return db.findUrls(key); } + private boolean getBookmark(UmbrellaUser user, long urlId, HttpExchange ex) throws IOException { + var bookmark = db.load(urlId,user.id()); + tagService().getTags(BOOKMARK, urlId, user).forEach(bookmark.tags()::add); + return sendContent(ex,bookmark); + } + private boolean getUserBookmarks(UmbrellaUser user, HttpExchange ex) throws IOException { var param = queryParam(ex); long offset = switch (param.get(OFFSET)){ @@ -110,6 +162,23 @@ public class BookmarkApi extends BaseHandler implements BookmarkService { return sendContent(ex,mapValues(bookmarks)); } + private boolean patchBookmark(UmbrellaUser user, HttpExchange ex, long urlId) throws IOException { + var bookmark = db.load(urlId,user.id()); + var tags = tagService().getTags(BOOKMARK,urlId,user); + var json = json(ex); + var comment = bookmark.comment(); + var url = bookmark.url(); + if (json.has(COMMENT) && json.get(COMMENT) instanceof String c) comment = c; + if (json.has(URL) && json.get(URL) instanceof String u) url = u; + var newBookmark = db.save(url,comment, List.of(user.id()),bookmark.timestamp()); + if (newBookmark.urlId() != urlId) { + tagService().save(BOOKMARK,newBookmark.urlId(),List.of(user.id()),tags); + db.remove(user, bookmark); + //messageBus().dispatch(new BookmarkEvent(user,newBookmark,CREATE)); + } else messageBus().dispatch(new BookmarkEvent(user,newBookmark,UPDATE)); + return sendContent(ex,newBookmark); + } + private boolean postBookmark(UmbrellaUser user, HttpExchange ex) throws IOException { var json = json(ex); if (!(json.has(URL) && json.get(URL) instanceof String url)) throw missingField(URL); @@ -123,10 +192,11 @@ public class BookmarkApi extends BaseHandler implements BookmarkService { } } var bookmark = db.save(url,comment, userList); + messageBus().dispatch(new BookmarkEvent(user,bookmark,CREATE)); if (json.has(TAGS) && json.get(TAGS) instanceof JSONArray tagList){ var list = tagList.toList().stream().map(Object::toString).toList(); - ModuleRegistry.tagService().save(BOOKMARK,bookmark.urlId(), userList, list); + tagService().save(BOOKMARK,bookmark.urlId(), userList, list); } return sendContent(ex,bookmark); } diff --git a/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/BookmarkDb.java b/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/BookmarkDb.java index 7ecef538..020ba968 100644 --- a/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/BookmarkDb.java +++ b/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/BookmarkDb.java @@ -2,6 +2,8 @@ package de.srsoftware.umbrella.bookmarks; import de.srsoftware.umbrella.core.model.Bookmark; +import de.srsoftware.umbrella.core.model.UmbrellaUser; + import java.time.LocalDateTime; import java.util.Collection; import java.util.Map; @@ -15,6 +17,8 @@ public interface BookmarkDb { Bookmark load(long id, long userId); + void remove(UmbrellaUser user, Bookmark bookmark); + Bookmark save(String url, String comment, Collection userIds, LocalDateTime datetime); default Bookmark save(String url, String comment, Collection userIds){ diff --git a/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/SqliteDb.java b/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/SqliteDb.java index d63f6d10..4ef4a8fd 100644 --- a/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/SqliteDb.java +++ b/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/SqliteDb.java @@ -7,15 +7,24 @@ import static de.srsoftware.tools.jdbc.Query.*; import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL; import static de.srsoftware.umbrella.bookmarks.Constants.*; import static de.srsoftware.umbrella.core.Errors.*; +import static de.srsoftware.umbrella.core.ModuleRegistry.tagService; import static de.srsoftware.umbrella.core.constants.Field.*; import static de.srsoftware.umbrella.core.constants.Text.BOOKMARK; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*; +import static de.srsoftware.umbrella.messagebus.MessageBus.messageBus; +import static de.srsoftware.umbrella.messagebus.events.Event.EventType.CREATE; +import static de.srsoftware.umbrella.messagebus.events.Event.EventType.UPDATE; import static java.text.MessageFormat.format; import static java.time.ZoneOffset.UTC; import de.srsoftware.umbrella.core.BaseDb; +import de.srsoftware.umbrella.core.constants.Module; import de.srsoftware.umbrella.core.model.Bookmark; import de.srsoftware.umbrella.core.model.Translatable; +import de.srsoftware.umbrella.core.model.UmbrellaUser; +import de.srsoftware.umbrella.messagebus.events.BookmarkEvent; +import de.srsoftware.umbrella.messagebus.events.Event; + import java.sql.Connection; import java.sql.SQLException; import java.time.LocalDateTime; @@ -117,16 +126,29 @@ CREATE TABLE IF NOT EXISTS {0} ( } @Override - public Bookmark load(long id, long userId) { + public Bookmark load(long urlId, long userId) { try { Bookmark result = null; - var rs = select(ALL).from(TABLE_URLS).leftJoin(ID,TABLE_URL_COMMENTS,URL_ID).where(ID,equal(id)).where(USER_ID,equal(userId)).exec(db); + var rs = select(ALL).from(TABLE_URLS).leftJoin(ID,TABLE_URL_COMMENTS,URL_ID).where(ID,equal(urlId)).where(USER_ID,equal(userId)).exec(db); if (rs.next()) result = Bookmark.of(rs); rs.close(); if (result != null) return result; - throw failedToLoadObject(Translatable.t(BOOKMARK),id); + throw failedToLoadObject(Translatable.t(BOOKMARK),urlId); } catch (SQLException e) { - throw failedToLoadObject(Translatable.t(BOOKMARK),id).causedBy(e); + throw failedToLoadObject(Translatable.t(BOOKMARK),urlId).causedBy(e); + } + } + + @Override + public void remove(UmbrellaUser user, Bookmark bookmark) { + try { + var urlId = bookmark.urlId(); + var userId = user.id(); + delete().from(TABLE_URL_COMMENTS).where(USER_ID, equal(userId)).where(URL_ID, equal(urlId)).execute(db); + messageBus().dispatch(new BookmarkEvent(user,bookmark, Event.EventType.DELETE)); + tagService().deleteEntity(Module.BOOKMARK,urlId,userId); + } catch (SQLException e){ + throw failedToDropObject(BOOKMARK); } } diff --git a/bus/src/main/java/de/srsoftware/umbrella/messagebus/events/BookmarkEvent.java b/bus/src/main/java/de/srsoftware/umbrella/messagebus/events/BookmarkEvent.java new file mode 100644 index 00000000..21723970 --- /dev/null +++ b/bus/src/main/java/de/srsoftware/umbrella/messagebus/events/BookmarkEvent.java @@ -0,0 +1,41 @@ +package de.srsoftware.umbrella.messagebus.events; + +import de.srsoftware.umbrella.core.constants.Field; +import de.srsoftware.umbrella.core.model.Bookmark; +import de.srsoftware.umbrella.core.model.Task; +import de.srsoftware.umbrella.core.model.Translatable; +import de.srsoftware.umbrella.core.model.UmbrellaUser; + +import java.util.Collection; +import java.util.List; + +import static de.srsoftware.umbrella.core.constants.Module.BOOKMARK; +import static de.srsoftware.umbrella.core.model.Translatable.t; + +public class BookmarkEvent extends Event { + + public BookmarkEvent(UmbrellaUser initiator, Bookmark bookmark, EventType type){ + super(initiator,BOOKMARK,bookmark,type); + } + @Override + public Collection audience() { + return List.of(initiator()); + } + + @Override + public Translatable describe() { + return switch (eventType()){ + case CREATE -> t("New bookmark created"); + case DELETE -> t("The bookmark '{url}' has been deleted", Field.URL, payload().url()); + case UPDATE -> t("Bookmark updated"); + default -> null; + }; + } + + @Override + public Translatable subject() { + return describe(); + } + + +} diff --git a/core/src/main/java/de/srsoftware/umbrella/core/api/TagService.java b/core/src/main/java/de/srsoftware/umbrella/core/api/TagService.java index 2198ef6f..08a5b130 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/api/TagService.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/api/TagService.java @@ -8,7 +8,9 @@ import java.util.List; import java.util.Map; public interface TagService { - void deleteEntity(String task, long taskId); + void deleteEntity(String module, long taskId); + + void deleteEntity(String module, long urlId, long userId); Map> getTagUses(UmbrellaUser user, String tag); diff --git a/frontend/src/routes/bookmark/Index.svelte b/frontend/src/routes/bookmark/Index.svelte index 31baa910..11fb5669 100644 --- a/frontend/src/routes/bookmark/Index.svelte +++ b/frontend/src/routes/bookmark/Index.svelte @@ -1,7 +1,7 @@ diff --git a/frontend/src/routes/bookmark/Template.svelte b/frontend/src/routes/bookmark/Template.svelte index e63a9668..d6ed7920 100644 --- a/frontend/src/routes/bookmark/Template.svelte +++ b/frontend/src/routes/bookmark/Template.svelte @@ -1,19 +1,41 @@ {#if bookmark}
{bookmark.url} + edit(bookmark)} title={t('edit_object',{object:t('bookmark')})} > + del(bookmark)} title={t('delete_object',{object:t('bookmark')})} > {bookmark.timestamp.replace('T',' ')} {@html target(bookmark.comment.rendered)} +
{/if} \ No newline at end of file diff --git a/frontend/src/routes/bookmark/View.svelte b/frontend/src/routes/bookmark/View.svelte index 3b10b467..88d71a0b 100644 --- a/frontend/src/routes/bookmark/View.svelte +++ b/frontend/src/routes/bookmark/View.svelte @@ -2,8 +2,11 @@ import { onMount } from 'svelte'; import Bookmark from './Template.svelte'; + import LineEditor from '../../Components/LineEditor.svelte'; + import MarkdownEditor from '../../Components/MarkdownEditor.svelte'; + import Tags from '../tags/TagList.svelte'; - import { api } from '../../urls.svelte'; + import { api, patch } from '../../urls.svelte'; import { error, yikes } from '../../warn.svelte'; import { t } from '../../translations.svelte'; @@ -24,7 +27,55 @@ } } + function visit(ev){ + window.open(bookmark.url, '_blank').focus(); + } + + async function update(field,value){ + var url = api(`bookmark/${id}`); + var res = await patch(url,{[field]:value}); + if (res.ok){ + yikes(); + bookmark = await res.json(); + if (id != bookmark.id){ + id = bookmark.id; + history.pushState({}, null, `/bookmark/${id}/view`); + } + return true; + } + error(res); + return false; + } + onMount(load); -