From eac9eaeb9fd20790bd7bc86f0fbf18b3dd86ac35 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Sun, 3 Aug 2025 13:07:11 +0200 Subject: [PATCH] implemented loading of bookmarks and bookmark list --- .../umbrella/bookmarks/BookmarkApi.java | 32 ++++++++++-- .../umbrella/bookmarks/BookmarkDb.java | 8 ++- .../umbrella/bookmarks/Constants.java | 2 - .../umbrella/bookmarks/SqliteDb.java | 51 ++++++++++++++----- .../srsoftware/umbrella/core/Constants.java | 4 ++ .../de/srsoftware/umbrella/core/Util.java | 16 +++--- .../umbrella/core/model/Bookmark.java | 37 ++++++++++++++ .../srsoftware/umbrella/core/model/Hash.java | 22 ++++++++ frontend/src/routes/bookmark/Index.svelte | 50 +++++++++++++++--- web/src/main/resources/web/css/default.css | 11 ++++ 10 files changed, 201 insertions(+), 32 deletions(-) create mode 100644 core/src/main/java/de/srsoftware/umbrella/core/model/Bookmark.java create mode 100644 core/src/main/java/de/srsoftware/umbrella/core/model/Hash.java 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 dd82a5e..45992ff 100644 --- a/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/BookmarkApi.java +++ b/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/BookmarkApi.java @@ -3,10 +3,12 @@ package de.srsoftware.umbrella.bookmarks; import static de.srsoftware.umbrella.bookmarks.Constants.*; import static de.srsoftware.umbrella.core.ConnectionProvider.connect; +import static de.srsoftware.umbrella.core.Constants.COMMENT; import static de.srsoftware.umbrella.core.Constants.URL; +import static de.srsoftware.umbrella.core.Paths.LIST; +import static de.srsoftware.umbrella.core.Util.mapValues; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException; import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; -import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED; import com.sun.net.httpserver.HttpExchange; import de.srsoftware.configuration.Configuration; @@ -30,6 +32,25 @@ public class BookmarkApi extends BaseHandler { users = userService; } + @Override + public boolean doGet(Path path, HttpExchange ex) throws IOException { + addCors(ex); + try { + Optional token = SessionToken.from(ex).map(Token::of); + var user = users.loadUser(token); + if (user.isEmpty()) return unauthorized(ex); + var head = path.pop(); + return switch (head) { + case LIST -> getUserBookmarks(user.get(),ex); + case null, default -> super.doPost(path,ex); + }; + } catch (NumberFormatException e){ + return sendContent(ex,HTTP_BAD_REQUEST,"Invalid project id"); + } catch (UmbrellaException e){ + return send(ex,e); + } + } + @Override public boolean doPost(Path path, HttpExchange ex) throws IOException { addCors(ex); @@ -49,11 +70,16 @@ public class BookmarkApi extends BaseHandler { } } + private boolean getUserBookmarks(UmbrellaUser user, HttpExchange ex) throws IOException { + var bookmarks = db.list(user.id()); + return sendContent(ex,mapValues(bookmarks)); + } + private boolean postBookmark(UmbrellaUser user, HttpExchange ex) throws IOException { var json = json(ex); if (!(json.has(URL) && json.get(URL) instanceof String url)) throw missingFieldException(URL); if (!(json.has(COMMENT) && json.get(COMMENT) instanceof String comment)) throw missingFieldException(COMMENT); - var urlHash = db.save(url,comment, user.id()); - return sendContent(ex,urlHash); + var bookmark = db.save(url,comment, user.id()); + 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 235a84c..c81d190 100644 --- a/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/BookmarkDb.java +++ b/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/BookmarkDb.java @@ -1,6 +1,12 @@ /* © SRSoftware 2025 */ package de.srsoftware.umbrella.bookmarks; +import de.srsoftware.umbrella.core.model.Bookmark; +import de.srsoftware.umbrella.core.model.Hash; +import java.util.Map; + public interface BookmarkDb { - String save(String url, String comment, long userId); + Bookmark save(String url, String comment, long userId); + + Map list(long id); } diff --git a/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/Constants.java b/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/Constants.java index 9252719..c04ba99 100644 --- a/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/Constants.java +++ b/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/Constants.java @@ -2,9 +2,7 @@ package de.srsoftware.umbrella.bookmarks; public class Constants { - public static final String COMMENT = "comment"; public static final String CONFIG_DATABASE = "umbrella.modules.bookmark.database"; - public static final String HASH = "hash"; public static final String SAVE = "save"; public static final String TABLE_URLS = "urls"; public static final String TABLE_URL_COMMENTS = "url_comments"; 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 238158a..0fa0f63 100644 --- a/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/SqliteDb.java +++ b/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/SqliteDb.java @@ -1,18 +1,26 @@ /* © SRSoftware 2025 */ package de.srsoftware.umbrella.bookmarks; -import static de.srsoftware.tools.jdbc.Query.insertInto; -import static de.srsoftware.tools.jdbc.Query.replaceInto; +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.Constants.*; import static de.srsoftware.umbrella.core.Constants.ERROR_FAILED_CREATE_TABLE; import static de.srsoftware.umbrella.core.Util.sha1; import static java.lang.System.Logger.Level.ERROR; +import static java.text.MessageFormat.format; +import static java.time.ZoneOffset.UTC; +import de.srsoftware.tools.jdbc.Condition; import de.srsoftware.umbrella.core.BaseDb; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; +import de.srsoftware.umbrella.core.model.Bookmark; +import de.srsoftware.umbrella.core.model.Hash; import java.sql.Connection; import java.sql.SQLException; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; public class SqliteDb extends BaseDb implements BookmarkDb { public SqliteDb(Connection conn) { @@ -32,14 +40,15 @@ public class SqliteDb extends BaseDb implements BookmarkDb { private void createUrlCommentsTable() { var sql = """ -CREATE TABLE IF NOT EXISTS "url_comments" ( - `hash` VARCHAR ( 255 ) NOT NULL, - `user_id` LONG NOT NULL, - `comment` TEXT NOT NULL, - PRIMARY KEY (`hash`,`user_id`) +CREATE TABLE IF NOT EXISTS {0} ( + `{1}` VARCHAR ( 255 ) NOT NULL, + `{2}` LONG NOT NULL, + `{3}` TEXT NOT NULL, + `{4}` DATETIME NOT NULL, + PRIMARY KEY (`{1}`,`{2}`) )"""; try { - var stmt = db.prepareStatement(sql); + var stmt = db.prepareStatement(format(sql,TABLE_URL_COMMENTS,HASH,USER_ID,COMMENT,TIMESTAMP)); stmt.execute(); stmt.close(); } catch (SQLException e) { @@ -61,15 +70,33 @@ CREATE TABLE IF NOT EXISTS "url_comments" ( } @Override - public String save(String url, String comment, long userId) { + public Map list(long userId) { + + try { + var map = new HashMap(); + var rs = select(ALL).from(TABLE_URL_COMMENTS).leftJoin(HASH,TABLE_URLS,HASH).where(USER_ID, Condition.equal(userId)).exec(db); + while (rs.next()){ + var bookmark = Bookmark.of(rs); + map.put(bookmark.hash(),bookmark); + } + rs.close();; + return map; + } catch (SQLException e) { + throw new UmbrellaException("Failed to load bookmark list"); + } + } + + @Override + public Bookmark save(String url, String comment, long userId) { var hash = sha1(url); try { + var timestamp = LocalDateTime.now(); replaceInto(TABLE_URLS,HASH,URL) .values(hash,url).execute(db).close(); - replaceInto(TABLE_URL_COMMENTS,HASH,USER_ID,COMMENT) - .values(hash,userId,comment) + replaceInto(TABLE_URL_COMMENTS,HASH,USER_ID,COMMENT, TIMESTAMP) + .values(hash,userId,comment,timestamp.toEpochSecond(UTC)) .execute(db).close(); - return hash; + return Bookmark.of(url,comment,timestamp); } catch (SQLException e) { throw new UmbrellaException("Failed to store url"); } diff --git a/core/src/main/java/de/srsoftware/umbrella/core/Constants.java b/core/src/main/java/de/srsoftware/umbrella/core/Constants.java index c79dd5a..8e16a13 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/Constants.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/Constants.java @@ -17,6 +17,7 @@ public class Constants { public static final String BODY = "body"; public static final String CODE = "code"; + public static final String COMMENT = "comment"; public static final String COMPANY = "company"; public static final String COMPANY_ID = "company_id"; public static final String CONTENT_TYPE = "Content-Type"; @@ -57,6 +58,8 @@ public class Constants { public static final String GET = "GET"; + public static final String HASH = "hash"; + public static final String ID = "id"; public static final String JSONARRAY = "json array"; @@ -112,6 +115,7 @@ public class Constants { public static final String TITLE = "title"; public static final String TIMESTAMP = "timestamp"; public static final String TOKEN = "token"; + public static final String TYPE = "type"; public static final String UMBRELLA = "Umbrella"; public static final String URL = "url"; diff --git a/core/src/main/java/de/srsoftware/umbrella/core/Util.java b/core/src/main/java/de/srsoftware/umbrella/core/Util.java index 016519f..bfb2886 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/Util.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/Util.java @@ -13,6 +13,7 @@ import com.xrbpowered.jparsedown.JParsedown; import de.srsoftware.tools.Mappable; import de.srsoftware.tools.Query; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; +import de.srsoftware.umbrella.core.model.Hash; import java.io.*; import java.net.HttpURLConnection; import java.net.URI; @@ -32,11 +33,12 @@ public class Util { private static final Pattern UML_PATTERN = Pattern.compile("@start(\\w+)(.*)@end(\\1)",Pattern.DOTALL); private static File plantumlJar = null; private static final JParsedown MARKDOWN = new JParsedown(); - private static final MessageDigest SHA1; + public static final String SHA1 = "SHA-1"; + private static final MessageDigest SHA1_DIGEST; static { try { - SHA1 = MessageDigest.getInstance("SHA-1"); + SHA1_DIGEST = MessageDigest.getInstance(SHA1); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } @@ -54,8 +56,8 @@ public class Util { }; } - public static Map> mapValues(Map map){ - var result = new HashMap>(); + public static Map> mapValues(Map map){ + var result = new HashMap>(); for (var entry : map.entrySet()) result.put(entry.getKey(),entry.getValue().toMap()); return result; } @@ -167,9 +169,9 @@ public class Util { } } - public static String sha1(String plain){ - var bytes = SHA1.digest(plain.getBytes(UTF_8)); - return hex(bytes); + public static Hash sha1(String plain){ + var bytes = SHA1_DIGEST.digest(plain.getBytes(UTF_8)); + return new Hash(hex(bytes),SHA1); } public static void setPlantUmlJar(File file){ diff --git a/core/src/main/java/de/srsoftware/umbrella/core/model/Bookmark.java b/core/src/main/java/de/srsoftware/umbrella/core/model/Bookmark.java new file mode 100644 index 0000000..9496d13 --- /dev/null +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/Bookmark.java @@ -0,0 +1,37 @@ +/* © SRSoftware 2025 */ +package de.srsoftware.umbrella.core.model; + +import static de.srsoftware.umbrella.core.Constants.*; +import static de.srsoftware.umbrella.core.Util.SHA1; +import static de.srsoftware.umbrella.core.Util.sha1; +import static java.time.ZoneOffset.UTC; + +import de.srsoftware.tools.Mappable; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +public record Bookmark(String url, Hash hash, String comment, LocalDateTime timestamp, Collection tags) implements Mappable { + + public static Bookmark of(ResultSet rs) throws SQLException { + return new Bookmark(rs.getString(URL),new Hash(rs.getString(HASH),SHA1),rs.getString(COMMENT),LocalDateTime.ofEpochSecond(rs.getLong(TIMESTAMP),0, UTC),new ArrayList<>()); + } + + public static Bookmark of(String url, String comment, LocalDateTime timestamp){ + return new Bookmark(url,sha1(url),comment,timestamp,new ArrayList<>()); + } + + @Override + public Map toMap() { + return Map.of( + URL, url, + COMMENT, comment, + HASH, Map.of(VALUE,hash.value(),TYPE,hash.type()), + TAGS, tags, + TIMESTAMP, timestamp.withNano(0) + ); + } +} diff --git a/core/src/main/java/de/srsoftware/umbrella/core/model/Hash.java b/core/src/main/java/de/srsoftware/umbrella/core/model/Hash.java new file mode 100644 index 0000000..56ec3fb --- /dev/null +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/Hash.java @@ -0,0 +1,22 @@ +/* © SRSoftware 2025 */ +package de.srsoftware.umbrella.core.model; + +import java.util.Objects; + +public record Hash(String value, String type){ + @Override + public boolean equals(Object o) { + if (!(o instanceof Hash(String v, String t))) return false; + return Objects.equals(type, t) && Objects.equals(value, v); + } + + @Override + public int hashCode() { + return Objects.hash(value, type); + } + + @Override + public String toString() { + return value; + } +} diff --git a/frontend/src/routes/bookmark/Index.svelte b/frontend/src/routes/bookmark/Index.svelte index a99c19e..22aee11 100644 --- a/frontend/src/routes/bookmark/Index.svelte +++ b/frontend/src/routes/bookmark/Index.svelte @@ -1,12 +1,31 @@
@@ -40,4 +63,17 @@ + {#if bookmarks} + {#each bookmarks as bookmark} +
+ + {bookmark.url} + + + {bookmark.timestamp.replace('T',' ')} + + {bookmark.comment} +
+ {/each} + {/if}
\ No newline at end of file diff --git a/web/src/main/resources/web/css/default.css b/web/src/main/resources/web/css/default.css index f90b157..1174653 100644 --- a/web/src/main/resources/web/css/default.css +++ b/web/src/main/resources/web/css/default.css @@ -244,3 +244,14 @@ textarea{ color: orange; border: 0 none; } + +fieldset.bookmark{ + position: relative; +} + +fieldset.bookmark legend.date{ + position: absolute; + right: 0; + top: -17px; + background: black; +} \ No newline at end of file