From 7e52e0268489f1d82d9e39ea8533954c31fc0610 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Fri, 12 Sep 2025 13:20:34 +0200 Subject: [PATCH] implemented adding new pages --- .../umbrella/bookmarks/BookmarkApi.java | 1 + .../de/srsoftware/umbrella/core/Paths.java | 1 + .../umbrella/core/model/WikiPage.java | 14 ++-- frontend/src/App.svelte | 2 + frontend/src/routes/company/Editor.svelte | 5 +- frontend/src/routes/project/Create.svelte | 1 - frontend/src/routes/task/Add.svelte | 1 + frontend/src/routes/wiki/AddPage.svelte | 66 +++++++++++++++++++ frontend/src/routes/wiki/Index.svelte | 2 + translations/src/main/resources/de.json | 3 + .../de/srsoftware/umbrella/wiki/SqliteDb.java | 46 ++++++++++--- .../de/srsoftware/umbrella/wiki/WikiDb.java | 4 ++ .../srsoftware/umbrella/wiki/WikiModule.java | 30 ++++++++- 13 files changed, 155 insertions(+), 21 deletions(-) create mode 100644 frontend/src/routes/wiki/AddPage.svelte 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 2a5907a..eea9fab 100644 --- a/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/BookmarkApi.java +++ b/bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/BookmarkApi.java @@ -34,6 +34,7 @@ public class BookmarkApi extends BaseHandler implements BookmarkService { super(); var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingFieldException(CONFIG_DATABASE)); db = new SqliteDb(connect(dbFile)); + ModuleRegistry.add(this); } @Override diff --git a/core/src/main/java/de/srsoftware/umbrella/core/Paths.java b/core/src/main/java/de/srsoftware/umbrella/core/Paths.java index 2281e9e..f4c441d 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/Paths.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/Paths.java @@ -5,6 +5,7 @@ public class Paths { private Paths(){}; public static final String ADD = "add"; + public static final String AVAILABLE = "available"; public static final String CSS = "css"; public static final String COMMON_TEMPLATES = "common_templates"; public static final String JSON = "json"; diff --git a/core/src/main/java/de/srsoftware/umbrella/core/model/WikiPage.java b/core/src/main/java/de/srsoftware/umbrella/core/model/WikiPage.java index 26a2e2c..07ec6e8 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/model/WikiPage.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/WikiPage.java @@ -15,7 +15,7 @@ import org.json.JSONObject; public class WikiPage implements Mappable { - private long id; + private final long id; private String title; private int version; private final List versions = new ArrayList<>(); @@ -50,11 +50,6 @@ public class WikiPage implements Mappable { return id; } - public WikiPage id(long newVal){ - id = newVal; - return this; - } - public boolean isDirty(String field) { return dirtyFields.contains(field); } @@ -109,6 +104,13 @@ public class WikiPage implements Mappable { return this; } + public WikiPage setNew(){ + dirtyFields.add(TITLE); + dirtyFields.add(CONTENT); + dirtyFields.add(MEMBERS); + return this; + } + public String title(){ return title; } diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index a7b0477..1f8df07 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -19,6 +19,7 @@ import Login from "./Components/Login.svelte"; import Messages from "./routes/message/Messages.svelte"; import Menu from "./Components/Menu.svelte"; + import NewPage from "./routes/wiki/AddPage.svelte"; import Notes from "./routes/notes/Index.svelte"; import ProjectList from "./routes/project/List.svelte"; import ProjectAdd from "./routes/project/Create.svelte"; @@ -90,6 +91,7 @@ + diff --git a/frontend/src/routes/company/Editor.svelte b/frontend/src/routes/company/Editor.svelte index 7ef9f63..813e7ac 100644 --- a/frontend/src/routes/company/Editor.svelte +++ b/frontend/src/routes/company/Editor.svelte @@ -39,10 +39,7 @@ }); if (resp.ok){ const patched = await resp.json(); - for (let key of Object.keys(patched)){ - console.log('patching '+key+'…'); - company[key] = patched[key]; - } + for (let key of Object.keys(patched)) company[key] = patched[key]; return true; } error = await resp.text(); diff --git a/frontend/src/routes/project/Create.svelte b/frontend/src/routes/project/Create.svelte index ddd8e1d..43e7c91 100644 --- a/frontend/src/routes/project/Create.svelte +++ b/frontend/src/routes/project/Create.svelte @@ -38,7 +38,6 @@ function onselect(company){ project.company_id = company.id; - console.log(project); } function toggleSettings(ev){ diff --git a/frontend/src/routes/task/Add.svelte b/frontend/src/routes/task/Add.svelte index 9012428..93e159b 100644 --- a/frontend/src/routes/task/Add.svelte +++ b/frontend/src/routes/task/Add.svelte @@ -35,6 +35,7 @@ function dropMember(member){ delete task.members[member.user.id]; console.log({drop:member.user.id}); + /// TODO: ? } async function load(){ diff --git a/frontend/src/routes/wiki/AddPage.svelte b/frontend/src/routes/wiki/AddPage.svelte new file mode 100644 index 0000000..8510146 --- /dev/null +++ b/frontend/src/routes/wiki/AddPage.svelte @@ -0,0 +1,66 @@ + + +

{t('create_new_object',{object:t('page')})}

+{#if error} +{error} +{/if} +
+ + +
\ No newline at end of file diff --git a/frontend/src/routes/wiki/Index.svelte b/frontend/src/routes/wiki/Index.svelte index 96379ea..7c9cedf 100644 --- a/frontend/src/routes/wiki/Index.svelte +++ b/frontend/src/routes/wiki/Index.svelte @@ -33,6 +33,8 @@ {error} {/if}

{t('wiki')}

+ + {#if pages} {#each pages as page} {#if page.charAt(0).toUpperCase() != lastLetter} diff --git a/translations/src/main/resources/de.json b/translations/src/main/resources/de.json index 797e1b7..bd86f97 100644 --- a/translations/src/main/resources/de.json +++ b/translations/src/main/resources/de.json @@ -162,6 +162,7 @@ "oidc_Login" : "Anmeldung mit OIDC", "old_password": "altes Passwort", + "page": "Seite", "parent_task": "übergeordnete Aufgabe", "password" : "Passwort", "permission": { @@ -233,6 +234,7 @@ "subtasks": "Unteraufgaben", "succeeding_document": "Nachfolge-Dokument", "sum_of_records": "Summe der ausgewählten Einträge", + "tag_uses": "Verwendung des Tags „{tag}“", "tags": "Tags", "task": "Aufgabe", @@ -244,6 +246,7 @@ "theme": "Design", "times": "Zeiten", "timetracking": "Zeiterfassung", + "title_not_available": "„{title}“ ist als Seitenname nicht mehr verfügbar!", "title_or_desc": "Titel/Beschreibung", "tutorial": "Tutorial", "type": "Dokumententyp", diff --git a/wiki/src/main/java/de/srsoftware/umbrella/wiki/SqliteDb.java b/wiki/src/main/java/de/srsoftware/umbrella/wiki/SqliteDb.java index 567af32..8d9deaf 100644 --- a/wiki/src/main/java/de/srsoftware/umbrella/wiki/SqliteDb.java +++ b/wiki/src/main/java/de/srsoftware/umbrella/wiki/SqliteDb.java @@ -1,6 +1,7 @@ /* © SRSoftware 2025 */ package de.srsoftware.umbrella.wiki; +import static de.srsoftware.tools.jdbc.Condition.equal; import static de.srsoftware.tools.jdbc.Query.*; import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL; import static de.srsoftware.umbrella.core.Constants.*; @@ -142,10 +143,37 @@ public class SqliteDb extends BaseDb implements WikiDb { } } - @Override + @Override + public long getNextId() { + try { + var id = 0L; + var rs = select("MAX(ID)").from(TABLE_PAGES).exec(db); + if (rs.next()) id = rs.getLong(1); + rs.close(); + return id+1; + } catch (SQLException e) { + throw databaseException("Failed to query next free page id!"); + } + } + + @Override + public boolean isAvailable(String title) { + if (title==null||title.isBlank())return false; + try { + var count = 1; + var rs = select("COUNT(ID)").from(TABLE_PAGES).where(TITLE,equal(title)).exec(db); + if (rs.next()) count = rs.getInt(1); + rs.close(); + return count < 1; + } catch (SQLException e) { + throw databaseException("Failed to query availability of {0}!",title); + } + } + + @Override public List listUserPages(long userId) { try { - var rs = select(TITLE,"MAX(version) AS version").from(TABLE_PAGES).leftJoin(ID,TABLE_PAGES_USERS,PAGE_ID).where(USER_ID, Condition.equal(userId)).groupBy(TITLE).sort("TITLE COLLATE NOCASE ASC").exec(db); + var rs = select(TITLE,"MAX(version) AS version").from(TABLE_PAGES).leftJoin(ID,TABLE_PAGES_USERS,PAGE_ID).where(USER_ID, equal(userId)).groupBy(TITLE).sort("TITLE COLLATE NOCASE ASC").exec(db); var set = new ArrayList(); while (rs.next()) set.add(rs.getString(TITLE)); rs.close(); @@ -160,11 +188,11 @@ public class SqliteDb extends BaseDb implements WikiDb { WikiPage page = null; try { // Try to load by id long id = Long.parseLong(title); - var query = select(ALL).from(TABLE_PAGES).where(ID,Condition.equal(id)); + var query = select(ALL).from(TABLE_PAGES).where(ID, equal(id)); if (version == null) { query.sort(VERSION+" DESC").limit(1); } else { - query.where(VERSION,Condition.equal(version)); + query.where(VERSION, equal(version)); } var rs = query.exec(db); if (rs.next()) page = WikiPage.of(rs); @@ -175,11 +203,11 @@ public class SqliteDb extends BaseDb implements WikiDb { throw databaseException("Failed to load wiki page \"{0}\" from database!",title); } if (page == null) try { // page was not loaded by ID - var query = select(ALL).from(TABLE_PAGES).where(TITLE,Condition.equal(title)); + var query = select(ALL).from(TABLE_PAGES).where(TITLE, equal(title)); if (version == null) { query.sort(VERSION+" DESC").limit(1); } else { - query.where(VERSION,Condition.equal(version)); + query.where(VERSION, equal(version)); } var rs = query.exec(db); if (rs.next()) page = WikiPage.of(rs); @@ -189,7 +217,7 @@ public class SqliteDb extends BaseDb implements WikiDb { } if (page == null) throw notFound("Failed to load wiki page \"{0}\" from database!",title); try { - var rs = select(VERSION).from(TABLE_PAGES).where(ID,Condition.equal(page.id())).sort(VERSION).exec(db); + var rs = select(VERSION).from(TABLE_PAGES).where(ID, equal(page.id())).sort(VERSION).exec(db); var versions = page.versions(); while (rs.next()) versions.add(rs.getInt(VERSION)); rs.close(); @@ -291,7 +319,7 @@ public class SqliteDb extends BaseDb implements WikiDb { public Map loadMembers(WikiPage page) { try { var map = new HashMap(); - var rs = select(ALL).from(TABLE_PAGES_USERS).where(PAGE_ID,Condition.equal(page.id())).exec(db); + var rs = select(ALL).from(TABLE_PAGES_USERS).where(PAGE_ID, equal(page.id())).exec(db); while (rs.next()){ var permission = wikiPermission(rs.getInt(PERMISSIONS)); if (permission != null) map.put(rs.getLong(USER_ID),permission); @@ -309,7 +337,7 @@ public class SqliteDb extends BaseDb implements WikiDb { if (page.isDirty(CONTENT) || page.isDirty(ID) || page.isDirty(TITLE)) insertInto(TABLE_PAGES,ID,VERSION,TITLE,CONTENT) .values(page.id(),page.version(),page.title(),page.content()).execute(db).close(); if (page.isDirty(MEMBERS)){ - Query.delete().from(TABLE_PAGES_USERS).where(PAGE_ID,Condition.equal(page.title())).where(USER_ID,Condition.notIn(page.members().keySet().toArray())).execute(db); + Query.delete().from(TABLE_PAGES_USERS).where(PAGE_ID, equal(page.title())).where(USER_ID,Condition.notIn(page.members().keySet().toArray())).execute(db); var query = replaceInto(TABLE_PAGES_USERS,PAGE_ID,USER_ID,PERMISSIONS); for (var member : page.members().entrySet()) query.values(page.id(),member.getKey(),wikiPermissionCode(member.getValue().permission())); query.execute(db).close(); diff --git a/wiki/src/main/java/de/srsoftware/umbrella/wiki/WikiDb.java b/wiki/src/main/java/de/srsoftware/umbrella/wiki/WikiDb.java index cee504b..dfd13b3 100644 --- a/wiki/src/main/java/de/srsoftware/umbrella/wiki/WikiDb.java +++ b/wiki/src/main/java/de/srsoftware/umbrella/wiki/WikiDb.java @@ -9,6 +9,10 @@ import java.util.Map; public interface WikiDb { + long getNextId(); + + boolean isAvailable(String title); + List listUserPages(long userId); WikiPage load(String id, Integer version); diff --git a/wiki/src/main/java/de/srsoftware/umbrella/wiki/WikiModule.java b/wiki/src/main/java/de/srsoftware/umbrella/wiki/WikiModule.java index 2e70be3..a1f6e4d 100644 --- a/wiki/src/main/java/de/srsoftware/umbrella/wiki/WikiModule.java +++ b/wiki/src/main/java/de/srsoftware/umbrella/wiki/WikiModule.java @@ -4,6 +4,7 @@ package de.srsoftware.umbrella.wiki; import static de.srsoftware.umbrella.core.ConnectionProvider.connect; import static de.srsoftware.umbrella.core.Constants.VERSION; import static de.srsoftware.umbrella.core.ModuleRegistry.userService; +import static de.srsoftware.umbrella.core.Paths.AVAILABLE; import static de.srsoftware.umbrella.core.Paths.PAGE; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*; import static de.srsoftware.umbrella.core.model.Permission.EDIT; @@ -43,6 +44,7 @@ public class WikiModule extends BaseHandler implements WikiService { var head = path.pop(); return switch (head) { case null -> getUserPages(user.get(),ex); + case AVAILABLE -> getAvailability(path,ex); case PAGE -> getPage(path, user.get(), ex); default -> super.doGet(path,ex); }; @@ -51,7 +53,7 @@ public class WikiModule extends BaseHandler implements WikiService { } } - @Override + @Override public boolean doPatch(Path path, HttpExchange ex) throws IOException { addCors(ex); try { @@ -69,6 +71,25 @@ public class WikiModule extends BaseHandler implements WikiService { } } + @Override + public boolean doPost(Path path, HttpExchange ex) throws IOException { + addCors(ex); + try { + Optional token = SessionToken.from(ex).map(Token::of); + var user = userService().loadUser(token); + if (user.isEmpty()) return unauthorized(ex); + var title = path.pop(); + if (!path.empty()) return super.doPost(path,ex); + return postNewPage(title,user.get(),ex); + } catch (UmbrellaException e){ + return send(ex,e); + } + } + + private boolean getAvailability(Path path, HttpExchange ex) throws IOException { + return sendContent(ex,wikiDb.isAvailable(path.pop())); + } + private boolean getPage(Path path, UmbrellaUser user, HttpExchange ex) throws IOException { var id = path.pop(); Integer version = null; @@ -118,4 +139,11 @@ public class WikiModule extends BaseHandler implements WikiService { var json = json(ex); return sendContent(ex,wikiDb.save(page.patch(json, userService()))); } + + private boolean postNewPage(String title, UmbrellaUser user, HttpExchange ex) throws IOException { + var content = body(ex); + var page = new WikiPage(wikiDb.getNextId(),title,1,content); + page.members().put(user.id(),new Member(user,EDIT)); + return sendContent(ex,wikiDb.save(page.setNew())); + } }