diff --git a/core/src/main/java/de/srsoftware/umbrella/core/model/Member.java b/core/src/main/java/de/srsoftware/umbrella/core/model/Member.java index fa37a5a..f01a9ba 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/model/Member.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/Member.java @@ -2,16 +2,14 @@ package de.srsoftware.umbrella.core.model; -import de.srsoftware.tools.Mappable; +import static de.srsoftware.umbrella.core.Constants.*; +import de.srsoftware.tools.Mappable; import java.util.Map; -import static de.srsoftware.umbrella.core.Constants.PERMISSION; -import static de.srsoftware.umbrella.core.Constants.USER; - -public record Member(UmbrellaUser user, Permission permission) implements Mappable { +public record Member(long userId, Permission permission) implements Mappable { @Override public Map toMap() { - return Map.of(USER,user.toMap(),PERMISSION,permission.name()); + return Map.of(USER_ID,userId,PERMISSION,permission.name()); } } diff --git a/core/src/main/java/de/srsoftware/umbrella/core/model/Project.java b/core/src/main/java/de/srsoftware/umbrella/core/model/Project.java index 9a7bf8b..7a8c752 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/model/Project.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/Project.java @@ -2,6 +2,7 @@ package de.srsoftware.umbrella.core.model; import static de.srsoftware.umbrella.core.Constants.*; +import static de.srsoftware.umbrella.core.Util.markdown; import de.srsoftware.tools.Mappable; import java.sql.ResultSet; @@ -47,7 +48,7 @@ public record Project(long id, String name, String description, Status status, L var map = new HashMap(); map.put(ID,id); map.put(NAME,name); - map.put(DESCRIPTION,description); + map.put(DESCRIPTION,Map.of(SOURCE,description,RENDERED,markdown(description))); map.put(STATUS,Map.of(STATUS_CODE,status.code(), NAME,status.name())); map.put(COMPANY_ID,companyId); map.put(SHOW_CLOSED,showClosed); diff --git a/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentApi.java b/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentApi.java index 7a831ae..157efe5 100644 --- a/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentApi.java +++ b/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentApi.java @@ -150,7 +150,6 @@ public class DocumentApi extends BaseHandler { case SETTINGS -> getDocumentSettings(ex,docId,user.get()); default -> super.doGet(path,ex); }; - } }; } catch (NumberFormatException ignored) { diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 30ca8a2..ed80cb0 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -19,6 +19,7 @@ import SendDoc from "./routes/document/Send.svelte"; import User from "./routes/user/User.svelte"; import ViewDoc from "./routes/document/View.svelte"; + import ViewPrj from "./routes/project/View.svelte"; let translations_ready = $state(false); onMount(async () => { @@ -48,6 +49,7 @@ + diff --git a/frontend/src/Components/Menu.svelte b/frontend/src/Components/Menu.svelte index 5a768e4..9e84a0e 100644 --- a/frontend/src/Components/Menu.svelte +++ b/frontend/src/Components/Menu.svelte @@ -13,7 +13,10 @@ async function fetchModules(){ const resp = await fetch(url,{credentials:'include'}); if (resp.ok){ const arr = await resp.json(); - for (let entry of arr) modules.push({name:t(entry.module),url:entry.url}); + for (let entry of arr) { + let name = t('module.'+entry.module); + if (name) modules.push({name:name,url:entry.url}); + } } else { console.log('error'); } @@ -32,9 +35,7 @@ onMount(fetchModules); router.navigate('/document')}>{t('documents')} router.navigate('/project')}>{t('projects')} {t('tutorial')} - {#each modules as module,i} - {module.name} - {/each} + {#each modules as module,i}{module.name}{/each} {#if user.name } {t('logout')} {/if} diff --git a/frontend/src/routes/project/List.svelte b/frontend/src/routes/project/List.svelte index 7b42b98..d45206c 100644 --- a/frontend/src/routes/project/List.svelte +++ b/frontend/src/routes/project/List.svelte @@ -48,8 +48,8 @@ - {#each Object.entries(projects) as [id,project]} - router.navigate(`/project/${project.id}/view`)}> + {#each Object.entries(projects) as [pid,project]} + router.navigate(`/project/${pid}/view`)}> {project.name} {#if project.company_id} @@ -60,7 +60,7 @@ {t("state_"+project.status.name.toLowerCase())} - {#each project.members as member,idx} + {#each Object.entries(project.members) as [uid,member]}
{member.user.name}
{/each} diff --git a/frontend/src/routes/project/View.svelte b/frontend/src/routes/project/View.svelte new file mode 100644 index 0000000..e62b718 --- /dev/null +++ b/frontend/src/routes/project/View.svelte @@ -0,0 +1,66 @@ + + +{#if error} +{error} +{/if} + +{#if project} + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{t('project')}{project.name}
{t('context')} + + + +
{t('description')}{@html project.description.rendered}
{t('estimated_time')}TODO
{t('tasks')}TODO
{t('members')} +
    + {#each Object.entries(project.members) as [uid,member]} +
  • {member.user.name}: {t('permission.'+member.permission)}
  • + {/each} +
+
+{/if} \ No newline at end of file diff --git a/project/src/main/java/de/srsoftware/umbrella/project/ProjectDb.java b/project/src/main/java/de/srsoftware/umbrella/project/ProjectDb.java index 1725db7..041976e 100644 --- a/project/src/main/java/de/srsoftware/umbrella/project/ProjectDb.java +++ b/project/src/main/java/de/srsoftware/umbrella/project/ProjectDb.java @@ -1,14 +1,15 @@ /* © SRSoftware 2025 */ package de.srsoftware.umbrella.project; -import de.srsoftware.umbrella.core.api.UserService; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.model.Project; import java.util.Map; public interface ProjectDb { - Map ofCompany(long companyId, boolean includeClosed, UserService userService) throws UmbrellaException; - Map ofUser(long userId, boolean includeClosed, UserService userService) throws UmbrellaException; + Map ofCompany(long companyId, boolean includeClosed) throws UmbrellaException; + Map ofUser(long userId, boolean includeClosed) throws UmbrellaException; Project save(Project prj) throws UmbrellaException; + + Project load(long projectId) throws UmbrellaException; } diff --git a/project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java b/project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java index cb3ed2b..69024aa 100644 --- a/project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java +++ b/project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java @@ -53,7 +53,14 @@ public class ProjectModule extends BaseHandler implements ProjectService { return switch (head) { case LIST -> listUserProjects(ex,user.get()); case null -> postProject(ex,user.get()); - default -> super.doGet(path,ex); + default -> { + var projectId = Long.parseLong(head); + head = path.pop(); + yield switch (head){ + case null -> getProject(ex,projectId,user.get()); + default -> super.doGet(path,ex); + }; + } }; } catch (UmbrellaException e){ return send(ex,e); @@ -78,8 +85,23 @@ public class ProjectModule extends BaseHandler implements ProjectService { } } + private boolean getProject(HttpExchange ex, long projectId, UmbrellaUser user) throws IOException, UmbrellaException { + var project = projects.load(projectId); + var map = project.toMap(); + var members = new HashMap>(); + for (var member : project.members()){ + var perm = member.permission().name(); + var userId = member.userId(); + members.put(userId,Map.of(USER,users.loadUser(userId).toMap(),PERMISSION,perm)); + } + if (!members.isEmpty()) map.put(MEMBERS,members); + + return sendContent(ex,map); + } + + public Collection listCompanyProjects(long companyId, boolean includeClosed) throws UmbrellaException { - return projects.ofCompany(companyId, includeClosed, users).values().stream().sorted(comparing(Project::name)).toList(); + return projects.ofCompany(companyId, includeClosed).values().stream().sorted(comparing(Project::name)).toList(); } private boolean listCompanyProjects(HttpExchange ex, UmbrellaUser user) throws IOException, UmbrellaException { @@ -97,12 +119,26 @@ public class ProjectModule extends BaseHandler implements ProjectService { @Override public Map listUserProjects(long userId, boolean includeClosed) throws UmbrellaException { - return projects.ofUser(userId, includeClosed, users); + return projects.ofUser(userId, includeClosed); } private boolean listUserProjects(HttpExchange ex, UmbrellaUser user) throws IOException, UmbrellaException { var projects = new HashMap>(); - for (var entry : listUserProjects(user.id(),false).entrySet()) projects.put(entry.getKey(),entry.getValue().toMap()); + for (var entry : listUserProjects(user.id(),false).entrySet()) { + var project = entry.getValue(); + var map = project.toMap(); + var members = new HashMap>(); + var userMap = new HashMap(); + for (var member : project.members()){ + var perm = member.permission().name(); + var userId = member.userId(); + var u = userMap.get(userId); + if (u == null) userMap.put(userId,u = users.loadUser(userId)); + members.put(userId,Map.of(USER,u.toMap(),PERMISSION,perm)); + } + if (!members.isEmpty()) map.put(MEMBERS,members); + projects.put(entry.getKey(),map); + } return sendContent(ex,projects); } @@ -120,7 +156,7 @@ public class ProjectModule extends BaseHandler implements ProjectService { if (json.has(SETTINGS) && json.get(SETTINGS) instanceof JSONObject settingsJson){ showClosed = settingsJson.has(SHOW_CLOSED) && settingsJson.get(SHOW_CLOSED) == TRUE; } - var prj = new Project(0,name,description,Project.Status.Open,companyId,showClosed, List.of(new Member(user, OWNER))); + var prj = new Project(0,name,description,Project.Status.Open,companyId,showClosed, List.of(new Member(user.id(), OWNER))); prj = projects.save(prj); return sendContent(ex,prj); } diff --git a/project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java b/project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java index b93bf4e..8a85ea3 100644 --- a/project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java +++ b/project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java @@ -14,7 +14,6 @@ import static java.lang.System.Logger.Level.INFO; import static java.text.MessageFormat.format; import de.srsoftware.tools.jdbc.Query; -import de.srsoftware.umbrella.core.api.UserService; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.model.Member; import de.srsoftware.umbrella.core.model.Permission; @@ -23,6 +22,7 @@ import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.HashMap; +import java.util.Map; public class SqliteDb implements ProjectDb { private static final System.Logger LOG = System.getLogger("Sqlite4Project"); @@ -35,29 +35,16 @@ public class SqliteDb implements ProjectDb { init(); } - private HashMap addMembers(HashMap projects, UserService userService) throws SQLException, UmbrellaException { + private Map addMembers(Map projects) throws SQLException, UmbrellaException { Object[] ids = projects.keySet().toArray(); var rs = select("*").from(TABLE_PROJECT_USERS).where(PROJECT_ID,in(ids)).exec(db); - var userIdMap = new HashMap>(); while (rs.next()){ var userId = rs.getLong(USER_ID); var projectId = rs.getLong(PROJECT_ID); var permission = Permission.of(rs.getInt(PERMISSIONS)); - HashMap userMap = userIdMap.computeIfAbsent(userId, k -> new HashMap<>()); - userMap.put(projectId,permission); + projects.get(projectId).members().add(new Member(userId,permission)); } rs.close(); - var userMap = userService.list(userIdMap.keySet()); - for (var entry : userIdMap.entrySet()){ - var userId = entry.getKey(); - var user = userMap.get(userId); - for (var inner : entry.getValue().entrySet()){ - var projectId = inner.getKey(); - var perm = inner.getValue(); - var project = projects.get(projectId); - project.members().add(new Member(user,perm)); - } - } return projects; } @@ -147,7 +134,22 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255) } @Override - public HashMap ofCompany(long companyId, boolean includeClosed, UserService userService) throws UmbrellaException { + public Project load(long projectId) throws UmbrellaException { + try { + var rs = select("*").from(TABLE_PROJECTS).where(ID, equal(projectId)).exec(db); + Project result = null; + if (rs.next()) result = Project.of(rs); + rs.close(); + if (result == null) throw UmbrellaException.notFound("No project found for id {0}",projectId); + addMembers(Map.of(projectId,result)); + return result; + } catch (SQLException e) { + throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to load items from database"); + } + } + + @Override + public Map ofCompany(long companyId, boolean includeClosed) throws UmbrellaException { try { var projects = new HashMap(); var query = select("*").from(TABLE_PROJECTS).where(COMPANY_ID, equal(companyId)); @@ -158,7 +160,7 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255) projects.put(project.id(),project); } rs.close(); - return addMembers(projects,userService); + return addMembers(projects); } catch (SQLException e) { throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to load items from database"); } @@ -167,7 +169,7 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255) @Override - public HashMap ofUser(long userId, boolean includeClosed, UserService userService) throws UmbrellaException { + public Map ofUser(long userId, boolean includeClosed) throws UmbrellaException { try { var projects = new HashMap(); var query = select("*").from(TABLE_PROJECTS).leftJoin(ID,TABLE_PROJECT_USERS,PROJECT_ID).where(USER_ID, equal(userId)); @@ -178,7 +180,7 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255) projects.put(project.id(),project); } rs.close(); - return addMembers(projects,userService); + return addMembers(projects); } catch (SQLException e) { throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to load items from database"); } @@ -195,8 +197,7 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255) if (id != null){ if (!prj.members().isEmpty()) { var query = insertInto(TABLE_PROJECT_USERS, PROJECT_ID, USER_ID, PERMISSIONS); - for (var member : prj.members()) - query.values(id, member.user().id(), member.permission().code()); + for (var member : prj.members()) query.values(id, member.userId(), member.permission().code()); query.execute(db).close(); } return new Project(id, prj.name(), prj.description(),prj.status(),prj.companyId(),prj.showClosed(),prj.members()); diff --git a/translations/src/main/resources/de.json b/translations/src/main/resources/de.json index 797d3cb..77ac2da 100644 --- a/translations/src/main/resources/de.json +++ b/translations/src/main/resources/de.json @@ -21,9 +21,11 @@ "confirm_deletion": "Soll '{pos}' wirklich gelöscht werden?", "company": "Firma", "company_optional": "Firma (optional)", + "confirmation": "Bestätigung", "contact": "Kontakte", "contained_tax": "enthaltene Steuer", "content": "Inhalt", + "context": "Kontext", "create": "anlegen", "create_new_document": "neues Dokument", "create_new_project": "neues Projekt anlegen", @@ -35,6 +37,7 @@ "customer_email": "Emailadresse des Kunden", "customer": "Kunde", "customer_id": "Kundennummer", + "data_sent": "Daten übermittelt", "date": "Datum", "delete": "löschen", @@ -95,6 +98,24 @@ "message": "Nachricht", "messages": "Benachrichtigungen", "model": "Modelle", + "module": { + "bookmark": "Lesezeichen", + "commons": " ", + "company": "Firma", + "contact": "Kontakte", + "document": "Dokumente", + "files": "Dateien", + "items": "Artikel", + "message": "Nachrichten", + "model": "Modelle", + "notes": "Notizen", + "project": "Projekte", + "stock": "Inventar", + "task": "Aufgaben", + "time": "Zeiterfassung", + "user": "Benutzer", + "wiki": "Wiki" + }, "mismatch": "ungleich", "must_not_be_empty": "darf nicht leer sein", @@ -111,13 +132,18 @@ "old_password": "altes Passwort", "password" : "Passwort", + "permission": { + "EDIT": "lesen/schreiben", + "OWNER": "Besitzer" + }, "permissions": "Berechtigungen", "pos": "Pos", "position": "Position", "positions": "Positionen", "price": "Preis", "processing_code": "Code wird verarbeitet…", - "project": "Projekte", + "project": "Projekt", + "projects": "Projekte", "repeat_new_password": "Wiederholung", "results": "Ergebnisse", @@ -153,7 +179,8 @@ "stock": "Inventar", "subject": "Betreff", - "task": "Aufgaben", + "task": "Aufgabe", + "tasks": "Aufgaben", "tax_id": "Steuernummer", "tax_rate": "Steuersatz", "theme": "Design", diff --git a/user/src/main/java/de/srsoftware/umbrella/user/UserModule.java b/user/src/main/java/de/srsoftware/umbrella/user/UserModule.java index 19aa7da..7c09cac 100644 --- a/user/src/main/java/de/srsoftware/umbrella/user/UserModule.java +++ b/user/src/main/java/de/srsoftware/umbrella/user/UserModule.java @@ -16,8 +16,8 @@ import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*; import static de.srsoftware.umbrella.user.Constants.*; import static de.srsoftware.umbrella.user.Paths.*; import static de.srsoftware.umbrella.user.Paths.IMPERSONATE; -import static de.srsoftware.umbrella.user.model.DbUser.Permission.*; import static de.srsoftware.umbrella.user.model.DbUser.Permission; +import static de.srsoftware.umbrella.user.model.DbUser.Permission.*; import static java.lang.System.Logger.Level.*; import static java.net.HttpURLConnection.*; import static java.nio.charset.StandardCharsets.UTF_8;