Browse Source

implemented adding new pages

module/wiki
Stephan Richter 2 months ago
parent
commit
7e52e02684
  1. 1
      bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/BookmarkApi.java
  2. 1
      core/src/main/java/de/srsoftware/umbrella/core/Paths.java
  3. 14
      core/src/main/java/de/srsoftware/umbrella/core/model/WikiPage.java
  4. 2
      frontend/src/App.svelte
  5. 5
      frontend/src/routes/company/Editor.svelte
  6. 1
      frontend/src/routes/project/Create.svelte
  7. 1
      frontend/src/routes/task/Add.svelte
  8. 66
      frontend/src/routes/wiki/AddPage.svelte
  9. 2
      frontend/src/routes/wiki/Index.svelte
  10. 3
      translations/src/main/resources/de.json
  11. 44
      wiki/src/main/java/de/srsoftware/umbrella/wiki/SqliteDb.java
  12. 4
      wiki/src/main/java/de/srsoftware/umbrella/wiki/WikiDb.java
  13. 28
      wiki/src/main/java/de/srsoftware/umbrella/wiki/WikiModule.java

1
bookmark/src/main/java/de/srsoftware/umbrella/bookmarks/BookmarkApi.java

@ -34,6 +34,7 @@ public class BookmarkApi extends BaseHandler implements BookmarkService { @@ -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

1
core/src/main/java/de/srsoftware/umbrella/core/Paths.java

@ -5,6 +5,7 @@ public class Paths { @@ -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";

14
core/src/main/java/de/srsoftware/umbrella/core/model/WikiPage.java

@ -15,7 +15,7 @@ import org.json.JSONObject; @@ -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<Integer> versions = new ArrayList<>();
@ -50,11 +50,6 @@ public class WikiPage implements Mappable { @@ -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 { @@ -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;
}

2
frontend/src/App.svelte

@ -19,6 +19,7 @@ @@ -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 @@ @@ -90,6 +91,7 @@
<Route path="/user/oidc/add" component={EditService} />
<Route path="/user/oidc/edit/:serviceName" component={EditService} />
<Route path="/wiki" component={WikiIndex} />
<Route path="/wiki/add" component={NewPage} />
<Route path="/wiki/:key/view" component={WikiPage} />
<Route path="/wiki/:key/version/:version" component={WikiPage} />
<Route>

5
frontend/src/routes/company/Editor.svelte

@ -39,10 +39,7 @@ @@ -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();

1
frontend/src/routes/project/Create.svelte

@ -38,7 +38,6 @@ @@ -38,7 +38,6 @@
function onselect(company){
project.company_id = company.id;
console.log(project);
}
function toggleSettings(ev){

1
frontend/src/routes/task/Add.svelte

@ -35,6 +35,7 @@ @@ -35,6 +35,7 @@
function dropMember(member){
delete task.members[member.user.id];
console.log({drop:member.user.id});
/// TODO: ?
}
async function load(){

66
frontend/src/routes/wiki/AddPage.svelte

@ -0,0 +1,66 @@ @@ -0,0 +1,66 @@
<script>
import { useTinyRouter } from 'svelte-tiny-router';
import { api } from '../../urls.svelte';
import { t } from '../../translations.svelte';
import Markdown from '../../Components/MarkdownEditor.svelte';
let content = $state({source:null,rendered:null});
let error = $state(null);
let router = useTinyRouter();
let timer = null;
let title = $state(null);
async function onsubmit(ev){
ev.preventDefault();
ev.stopPropagation();
const url = api(`wiki/${title}`);
const res = await fetch(url,{
credentials:'include',
method:'POST',
body:content.source
});
if (res.ok){
router.navigate(`/wiki/${title}/view`);
} else {
error = await res.text();
}
}
function checkTitle(){
if (title) {
if (timer) clearTimeout(timer);
timer = setTimeout(requestTitle,1000);
}
}
async function requestTitle(){
timer = null;
var url = api(`wiki/available/${title}`);
var res = await fetch(url,{credentials:'include'});
const body = await res.text();
if (res.ok){
error = body == 'true' ? null : t('title_not_available',{title:title})
} else {
error = body;
if (!error) error = t('failed_to_check_availability');
}
}
$effect(checkTitle)
</script>
<h1>{t('create_new_object',{object:t('page')})}</h1>
{#if error}
<span class="error">{error}</span>
{/if}
<form {onsubmit}>
<label>
{t('Name')}
<input type="text" bind:value={title} />
</label>
<label>
{t('content')}
<Markdown bind:value={content} simple={true} />
</label>
</form>

2
frontend/src/routes/wiki/Index.svelte

@ -33,6 +33,8 @@ @@ -33,6 +33,8 @@
<span class="error">{error}</span>
{/if}
<h1>{t('wiki')}</h1>
<button onclick={() => router.navigate('/wiki/add')}><span class="symbol"></span> {t('create_new_object',{object:t('page')})}</button>
{#if pages}
{#each pages as page}
{#if page.charAt(0).toUpperCase() != lastLetter}

3
translations/src/main/resources/de.json

@ -162,6 +162,7 @@ @@ -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 @@ @@ -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 @@ @@ -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",

44
wiki/src/main/java/de/srsoftware/umbrella/wiki/SqliteDb.java

@ -1,6 +1,7 @@ @@ -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 { @@ -142,10 +143,37 @@ public class SqliteDb extends BaseDb implements WikiDb {
}
}
@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<String> 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<String>();
while (rs.next()) set.add(rs.getString(TITLE));
rs.close();
@ -160,11 +188,11 @@ public class SqliteDb extends BaseDb implements WikiDb { @@ -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 { @@ -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 { @@ -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 { @@ -291,7 +319,7 @@ public class SqliteDb extends BaseDb implements WikiDb {
public Map<Long, Permission> loadMembers(WikiPage page) {
try {
var map = new HashMap<Long, Permission>();
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 { @@ -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();

4
wiki/src/main/java/de/srsoftware/umbrella/wiki/WikiDb.java

@ -9,6 +9,10 @@ import java.util.Map; @@ -9,6 +9,10 @@ import java.util.Map;
public interface WikiDb {
long getNextId();
boolean isAvailable(String title);
List<String> listUserPages(long userId);
WikiPage load(String id, Integer version);

28
wiki/src/main/java/de/srsoftware/umbrella/wiki/WikiModule.java

@ -4,6 +4,7 @@ package de.srsoftware.umbrella.wiki; @@ -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 { @@ -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);
};
@ -69,6 +71,25 @@ public class WikiModule extends BaseHandler implements WikiService { @@ -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> 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 { @@ -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()));
}
}

Loading…
Cancel
Save