From 6016f81c27dee5bdde19a2f650bbbf8451723c05 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Mon, 21 Jul 2025 00:16:14 +0200 Subject: [PATCH] working on project settings update: adding/updating members not implemented --- .../srsoftware/umbrella/core/Constants.java | 1 + .../umbrella/core/model/Member.java | 2 +- .../umbrella/core/model/Permission.java | 12 +++++- .../umbrella/core/model/Project.java | 40 ++++++++++++++---- frontend/src/Components/ListTask.svelte | 3 +- frontend/src/Components/MemberEditor.svelte | 42 +++++++++++++++++++ .../src/Components/PermissionSelector.svelte | 26 ++++++++++++ frontend/src/routes/project/View.svelte | 25 ++++++----- .../srsoftware/umbrella/items/Constants.java | 1 - .../de/srsoftware/umbrella/items/Item.java | 1 + .../umbrella/project/ProjectModule.java | 14 +++---- .../srsoftware/umbrella/project/SqliteDb.java | 9 +++- .../srsoftware/umbrella/task/TaskModule.java | 8 ++++ translations/src/main/resources/de.json | 6 ++- .../srsoftware/umbrella/user/Constants.java | 1 - .../srsoftware/umbrella/user/UserModule.java | 1 + web/src/main/resources/web/css/default.css | 11 +++++ 17 files changed, 169 insertions(+), 34 deletions(-) create mode 100644 frontend/src/Components/MemberEditor.svelte create mode 100644 frontend/src/Components/PermissionSelector.svelte 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 a97154d..59892d2 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/Constants.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/Constants.java @@ -12,6 +12,7 @@ public class Constants { public static final String ATTACHMENTS = "attachments"; public static final String AUTHORIZATION = "Authorization"; public static final String BODY = "body"; + public static final String CODE = "code"; public static final String COMPANY = "company"; public static final String COMPANY_ID = "company_id"; public static final String CONTENT_TYPE = "Content-Type"; 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 f01a9ba..f41b5a0 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 @@ -10,6 +10,6 @@ import java.util.Map; public record Member(long userId, Permission permission) implements Mappable { @Override public Map toMap() { - return Map.of(USER_ID,userId,PERMISSION,permission.name()); + return Map.of(USER_ID,userId,PERMISSION,permission.toMap()); } } diff --git a/core/src/main/java/de/srsoftware/umbrella/core/model/Permission.java b/core/src/main/java/de/srsoftware/umbrella/core/model/Permission.java index 9c8dcce..21d26d0 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/model/Permission.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/Permission.java @@ -1,11 +1,16 @@ /* © SRSoftware 2025 */ package de.srsoftware.umbrella.core.model; +import de.srsoftware.tools.Mappable; + +import static de.srsoftware.umbrella.core.Constants.CODE; +import static de.srsoftware.umbrella.core.Constants.NAME; import static java.text.MessageFormat.format; import java.security.InvalidParameterException; +import java.util.Map; -public enum Permission { +public enum Permission implements Mappable { OWNER(1), EDIT(2), ASSIGNEE(3), @@ -27,4 +32,9 @@ public enum Permission { } throw new InvalidParameterException(format("{0} is not a valid permission code")); } + + @Override + public Map toMap() { + return Map.of(NAME,name(),CODE,code); + } } 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 6c7559b..82c662d 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 @@ -1,18 +1,23 @@ /* © SRSoftware 2025 */ package de.srsoftware.umbrella.core.model; +import static de.srsoftware.tools.Optionals.isSet; import static de.srsoftware.tools.Optionals.nullable; import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.Util.markdown; +import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.invalidFieldException; +import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException; import de.srsoftware.tools.Mappable; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; + +import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import org.json.JSONObject; public class Project implements Mappable { - private final Collection members; + private final Map members; private final boolean showClosed; private final Long companyId; private Status status; @@ -21,7 +26,7 @@ public class Project implements Mappable { private String description; private final Set dirtyFields = new HashSet<>(); - public Project(long id, String name, String description, Status status, Long companyId, boolean showClosed, Collection members) { + public Project(long id, String name, String description, Status status, Long companyId, boolean showClosed, Map members) { this.id = id; this.name = name; this.description = description; @@ -44,17 +49,15 @@ public class Project implements Mappable { } public boolean hasMember(UmbrellaUser user) { - for (var member : members){ - if (member.userId() == user.id()) return true; - } - return false; + var member = members.get(user.id()); + return isSet(member) && member.userId() == user.id(); } public long id(){ return id; } - public Collection members(){ + public Map members(){ return members; } @@ -64,13 +67,14 @@ public class Project implements Mappable { public static Project of(ResultSet rs) throws SQLException { var companyId = rs.getLong(COMPANY_ID); - return new Project(rs.getLong(ID),rs.getString(NAME),rs.getString(DESCRIPTION),Status.of(rs.getInt(STATUS)),companyId == 0 ? null : companyId,rs.getBoolean(SHOW_CLOSED),new ArrayList<>()); + return new Project(rs.getLong(ID),rs.getString(NAME),rs.getString(DESCRIPTION),Status.of(rs.getInt(STATUS)),companyId == 0 ? null : companyId,rs.getBoolean(SHOW_CLOSED),new HashMap<>()); } public Project patch(JSONObject json) { for (var key : json.keySet()){ switch (key){ case DESCRIPTION: description = json.getString(key); break; + case MEMBERS: patchMembers(json.getJSONObject(MEMBERS)); break; case NAME: name = json.getString(key); break; case STATUS: status = json.get(key) instanceof Number number ? Status.of(number.intValue()) : Status.valueOf(json.getString(key)); break; default: key = null; @@ -80,6 +84,20 @@ public class Project implements Mappable { return this; } + private void patchMembers(JSONObject json) throws UmbrellaException { + for (var key : json.keySet()){ + long userId; + try { + userId = Long.parseLong(key); + } catch (NumberFormatException e) { + throw invalidFieldException(USER_ID,"long"); + } + if (!(json.get(key) instanceof Number number)) throw invalidFieldException(PERMISSION,"int"); + var permission = Permission.of(number.intValue()); + members.put(userId,new Member(userId,permission)); + } + } + public boolean showClosed(){ return showClosed; } @@ -91,13 +109,17 @@ public class Project implements Mappable { @Override public Map toMap() { var map = new HashMap(); + var memberMap = new HashMap>(); + if (members != null) for (var entry : members.entrySet()){ + memberMap.put(entry.getKey(),entry.getValue().toMap()); + } map.put(ID,id); map.put(NAME,name); 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); - map.put(MEMBERS,members == null ? List.of() : members.stream().map(Member::toMap).toList()); + map.put(MEMBERS,memberMap); return map; } diff --git a/frontend/src/Components/ListTask.svelte b/frontend/src/Components/ListTask.svelte index 2fb56fc..9d20a8a 100644 --- a/frontend/src/Components/ListTask.svelte +++ b/frontend/src/Components/ListTask.svelte @@ -29,7 +29,8 @@ function openTask(evt){ evt.preventDefault(); - router.navigate(`/task/${task.id}/view`); + //router.navigate(`/task/${task.id}/view`); + location.href = `https://umbrella.srsoftware.de/task/${task.id}/view`; } if (task.estimated_time){ diff --git a/frontend/src/Components/MemberEditor.svelte b/frontend/src/Components/MemberEditor.svelte new file mode 100644 index 0000000..0d3a85b --- /dev/null +++ b/frontend/src/Components/MemberEditor.svelte @@ -0,0 +1,42 @@ + + +{#if error} +{error} +{/if} + + + {#each sortedMembers as member,i} + + {#if !i} + + {/if} + + + + {/each} + +
{t('members')}{member.user.name} + updatePermission(member.user.id,perm)} /> +
\ No newline at end of file diff --git a/frontend/src/Components/PermissionSelector.svelte b/frontend/src/Components/PermissionSelector.svelte new file mode 100644 index 0000000..7f120b9 --- /dev/null +++ b/frontend/src/Components/PermissionSelector.svelte @@ -0,0 +1,26 @@ + + +{#if permissions} + +{:else} +{message} +{/if} \ No newline at end of file diff --git a/frontend/src/routes/project/View.svelte b/frontend/src/routes/project/View.svelte index 5b57750..c776b10 100644 --- a/frontend/src/routes/project/View.svelte +++ b/frontend/src/routes/project/View.svelte @@ -5,12 +5,14 @@ import MarkdownEditor from '../../Components/MarkdownEditor.svelte'; import LineEditor from '../../Components/LineEditor.svelte'; import StateSelector from '../../Components/StateSelector.svelte'; + import MemberEditor from '../../Components/MemberEditor.svelte'; let { id } = $props(); let project = $state(null); let error = $state(null); let tasks = $state(null); let estimated_time = $state({sum:0}); + let showSettings = $state(false); async function loadProject(){ const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/project/${id}`; @@ -54,6 +56,12 @@ } } + function updatePermission(user_id,permission){ + let members = {}; + members[user_id] = permission.code; + update({members:members}); + } + onMount(loadProject); @@ -68,6 +76,7 @@ {t('project')} update({name:val})} /> + @@ -110,16 +119,12 @@ {/if} - - {t('members')} - -
    - {#each Object.entries(project.members) as [uid,member]} -
  • {member.user.name}: {t('permission.'+member.permission)}
  • - {/each} -
- - +{/if} +{#if showSettings} +
+ {t('settings')} + +
{/if} \ No newline at end of file diff --git a/items/src/main/java/de/srsoftware/umbrella/items/Constants.java b/items/src/main/java/de/srsoftware/umbrella/items/Constants.java index 1cf8423..7d21f03 100644 --- a/items/src/main/java/de/srsoftware/umbrella/items/Constants.java +++ b/items/src/main/java/de/srsoftware/umbrella/items/Constants.java @@ -3,7 +3,6 @@ package de.srsoftware.umbrella.items; public class Constants { private Constants(){} - public static final String CODE = "code"; public static final String CONFIG_DATABASE = "umbrella.modules.items.database"; public static final String TABLE_ITEMS = "items"; public static final String TAX = "tax"; diff --git a/items/src/main/java/de/srsoftware/umbrella/items/Item.java b/items/src/main/java/de/srsoftware/umbrella/items/Item.java index 9678929..fe718e2 100644 --- a/items/src/main/java/de/srsoftware/umbrella/items/Item.java +++ b/items/src/main/java/de/srsoftware/umbrella/items/Item.java @@ -2,6 +2,7 @@ package de.srsoftware.umbrella.items; import static de.srsoftware.umbrella.core.Constants.*; +import static de.srsoftware.umbrella.core.Constants.CODE; import static de.srsoftware.umbrella.core.Util.markdown; import static de.srsoftware.umbrella.items.Constants.*; 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 ce32c6f..a70530e 100644 --- a/project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java +++ b/project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java @@ -118,9 +118,9 @@ public class ProjectModule extends BaseHandler implements ProjectService { private boolean addMembers(Project project, HttpExchange ex) throws IOException { var map = project.toMap(); var members = new HashMap>(); - for (var member : project.members()){ - var perm = member.permission().name(); - var userId = member.userId(); + for (var entry : project.members().entrySet()){ + var userId = entry.getKey(); + var perm = entry.getValue().permission().toMap(); members.put(userId,Map.of(USER,users.loadUser(userId).toMap(),PERMISSION,perm)); } if (!members.isEmpty()) map.put(MEMBERS,members); @@ -170,9 +170,9 @@ public class ProjectModule extends BaseHandler implements ProjectService { 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(); + for (var memberEntry : project.members().entrySet()){ + var perm = memberEntry.getValue().permission().name(); + var userId = memberEntry.getKey(); 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)); @@ -205,7 +205,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, OPEN,companyId,showClosed, List.of(new Member(user.id(), OWNER))); + var prj = new Project(0,name,description, OPEN,companyId,showClosed, Map.of(user.id(),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 e400f54..9f8f31e 100644 --- a/project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java +++ b/project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java @@ -43,7 +43,7 @@ public class SqliteDb implements ProjectDb { var userId = rs.getLong(USER_ID); var projectId = rs.getLong(PROJECT_ID); var permission = Permission.of(rs.getInt(PERMISSIONS)); - projects.get(projectId).members().add(new Member(userId,permission)); + projects.get(projectId).members().put(userId, new Member(userId,permission)); } rs.close(); return projects; @@ -198,7 +198,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.userId(), member.permission().code()); + for (var entry : prj.members().entrySet()) query.values(id, entry.getKey(), entry.getValue().permission().code()); query.execute(db).close(); } return new Project(id, prj.name(), prj.description(),prj.status(),prj.companyId().orElse(null),prj.showClosed(),prj.members()); @@ -208,6 +208,11 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255) } } else { // Update try { + if (prj.dirtyFields().contains(MEMBERS)){ + // TODO: + LOG.log(ERROR,"Updating/Adding project members not implemented!"); + prj.dirtyFields().remove(MEMBERS); + } if (prj.isDirty()){ update(TABLE_PROJECTS).set(NAME,DESCRIPTION,STATUS,COMPANY_ID,SHOW_CLOSED).where(ID,equal(prj.id())).prepare(db) .apply(prj.name(),prj.description(),prj.status().code(),prj.companyId(),prj.showClosed()) diff --git a/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java b/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java index 1cb8db4..bde2b96 100644 --- a/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java +++ b/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java @@ -11,6 +11,7 @@ import static de.srsoftware.umbrella.core.ResponseCode.HTTP_NOT_IMPLEMENTED; import static de.srsoftware.umbrella.core.Util.mapValues; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.forbidden; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException; +import static de.srsoftware.umbrella.project.Constants.PERMISSIONS; import static de.srsoftware.umbrella.task.Constants.*; import com.sun.net.httpserver.HttpExchange; @@ -56,6 +57,7 @@ public class TaskModule extends BaseHandler implements TaskService { if (user.isEmpty()) return unauthorized(ex); var head = path.pop(); return switch (head) { + case PERMISSIONS -> getPermissionList(ex); case STATES -> getStateList(ex); default -> super.doGet(path,ex); }; @@ -104,6 +106,12 @@ public class TaskModule extends BaseHandler implements TaskService { return sendContent(ex,result); } + private boolean getPermissionList(HttpExchange ex) throws IOException { + var map = new HashMap(); + for (var permission : Permission.values()) map.put(permission.code(),permission.name()); + return sendContent(ex,map); + } + private boolean getStateList(HttpExchange ex) throws IOException { var map = new HashMap(); for (var status : Status.values()) map.put(status.code(),status.name()); diff --git a/translations/src/main/resources/de.json b/translations/src/main/resources/de.json index 080e406..94a0036 100644 --- a/translations/src/main/resources/de.json +++ b/translations/src/main/resources/de.json @@ -140,6 +140,10 @@ "OWNER": "Besitzer" }, "permissions": "Berechtigungen", + "permission_assignee": "Verantwortlichter", + "permission_edit": "bearbeiten", + "permission_owner": "Besitzer", + "permission_read_only": "lesen", "pos": "Pos", "position": "Position", "positions": "Positionen", @@ -165,7 +169,7 @@ "sender_tax_id": "Steuernummer", "sent_email": "Email gesendet", "service": "Service", - "settings" : "Eisntellungen", + "settings" : "Einstellungen", "state": "Status", "state_cancelled": "abgebrochen", "state_complete": "abgeschlossen", diff --git a/user/src/main/java/de/srsoftware/umbrella/user/Constants.java b/user/src/main/java/de/srsoftware/umbrella/user/Constants.java index 6a91b36..797f37c 100644 --- a/user/src/main/java/de/srsoftware/umbrella/user/Constants.java +++ b/user/src/main/java/de/srsoftware/umbrella/user/Constants.java @@ -11,7 +11,6 @@ public class Constants { public static final String CLIENT_ID = "client_id"; public static final String CLIENT_SECRET = "client_secret"; - public static final String CODE = "code"; public static final String CONFIG_DATABASE = "umbrella.modules.user.database"; 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 7c09cac..035005f 100644 --- a/user/src/main/java/de/srsoftware/umbrella/user/UserModule.java +++ b/user/src/main/java/de/srsoftware/umbrella/user/UserModule.java @@ -6,6 +6,7 @@ import static de.srsoftware.tools.Optionals.*; import static de.srsoftware.tools.Strings.uuid; import static de.srsoftware.umbrella.core.ConnectionProvider.connect; import static de.srsoftware.umbrella.core.Constants.*; +import static de.srsoftware.umbrella.core.Constants.CODE; import static de.srsoftware.umbrella.core.Paths.LIST; import static de.srsoftware.umbrella.core.Paths.LOGOUT; import static de.srsoftware.umbrella.core.ResponseCode.*; diff --git a/web/src/main/resources/web/css/default.css b/web/src/main/resources/web/css/default.css index 45a9a2c..2866d89 100644 --- a/web/src/main/resources/web/css/default.css +++ b/web/src/main/resources/web/css/default.css @@ -99,4 +99,15 @@ td, tr{ font-family: awesome; font-size: 20px; font-weight: normal; +} + +.settings { + position: fixed; + background: black; + top: 60px; + left: 10px; + right: 10px; + padding: 10px; + border: 1px solid orange; + border-radius: 5px; } \ No newline at end of file