refactoring
This commit is contained in:
@@ -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<String, Object> toMap() {
|
||||
return Map.of(USER,user.toMap(),PERMISSION,permission.name());
|
||||
return Map.of(USER_ID,userId,PERMISSION,permission.name());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, Object>();
|
||||
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);
|
||||
|
||||
@@ -150,7 +150,6 @@ public class DocumentApi extends BaseHandler {
|
||||
case SETTINGS -> getDocumentSettings(ex,docId,user.get());
|
||||
default -> super.doGet(path,ex);
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
} catch (NumberFormatException ignored) {
|
||||
|
||||
@@ -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 @@
|
||||
<Route path="/message/settings" component={Messages} />
|
||||
<Route path="/project" component={ProjectList} />
|
||||
<Route path="/project/add" component={ProjectAdd} />
|
||||
<Route path="/project/:id/view" component={ViewPrj} />
|
||||
<Route path="/search" component={Search} />
|
||||
<Route path="/user" component={User} />
|
||||
<Route path="/user/create" component={EditUser} />
|
||||
|
||||
@@ -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);
|
||||
<a onclick={() => router.navigate('/document')}>{t('documents')}</a>
|
||||
<a onclick={() => router.navigate('/project')}>{t('projects')}</a>
|
||||
<a href="https://svelte.dev/tutorial/svelte/state" target="_blank">{t('tutorial')}</a>
|
||||
{#each modules as module,i}
|
||||
<a href={module.url}>{module.name}</a>
|
||||
{/each}
|
||||
{#each modules as module,i}<a href={module.url}>{module.name}</a>{/each}
|
||||
{#if user.name }
|
||||
<a onclick={logout}>{t('logout')}</a>
|
||||
{/if}
|
||||
|
||||
@@ -48,8 +48,8 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each Object.entries(projects) as [id,project]}
|
||||
<tr onclick={() => router.navigate(`/project/${project.id}/view`)}>
|
||||
{#each Object.entries(projects) as [pid,project]}
|
||||
<tr onclick={() => router.navigate(`/project/${pid}/view`)}>
|
||||
<td>{project.name}</td>
|
||||
<td>
|
||||
{#if project.company_id}
|
||||
@@ -60,7 +60,7 @@
|
||||
{t("state_"+project.status.name.toLowerCase())}
|
||||
</td>
|
||||
<td>
|
||||
{#each project.members as member,idx}
|
||||
{#each Object.entries(project.members) as [uid,member]}
|
||||
<div>{member.user.name}</div>
|
||||
{/each}
|
||||
</td>
|
||||
|
||||
66
frontend/src/routes/project/View.svelte
Normal file
66
frontend/src/routes/project/View.svelte
Normal file
@@ -0,0 +1,66 @@
|
||||
<script>
|
||||
import { t } from '../../translations.svelte.js';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let { id } = $props();
|
||||
let project = $state(null);
|
||||
let error = $state(null);
|
||||
async function loadProject(){
|
||||
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/project/${id}`;
|
||||
const resp = await fetch(url,{credentials:'include'});
|
||||
if (resp.ok){
|
||||
project = await resp.json();
|
||||
error = null;
|
||||
} else {
|
||||
error = await resp.text();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onMount(loadProject);
|
||||
</script>
|
||||
|
||||
{#if error}
|
||||
<span class="error">{error}</span>
|
||||
{/if}
|
||||
|
||||
{#if project}
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{t('project')}</th>
|
||||
<td>{project.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('context')}</th>
|
||||
<td>
|
||||
<button>{t('files')}</button>
|
||||
<button>{t('models')}</button>
|
||||
<button>{t('times')}</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('description')}</th>
|
||||
<td>{@html project.description.rendered}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('estimated_time')}</th>
|
||||
<td class="error">TODO</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('tasks')}</th>
|
||||
<td class="error">TODO</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('members')}</th>
|
||||
<td>
|
||||
<ul>
|
||||
{#each Object.entries(project.members) as [uid,member]}
|
||||
<li>{member.user.name}: {t('permission.'+member.permission)}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{/if}
|
||||
@@ -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<Long, Project> ofCompany(long companyId, boolean includeClosed, UserService userService) throws UmbrellaException;
|
||||
Map<Long, Project> ofUser(long userId, boolean includeClosed, UserService userService) throws UmbrellaException;
|
||||
Map<Long, Project> ofCompany(long companyId, boolean includeClosed) throws UmbrellaException;
|
||||
Map<Long, Project> ofUser(long userId, boolean includeClosed) throws UmbrellaException;
|
||||
|
||||
Project save(Project prj) throws UmbrellaException;
|
||||
|
||||
Project load(long projectId) throws UmbrellaException;
|
||||
}
|
||||
|
||||
@@ -53,8 +53,15 @@ public class ProjectModule extends BaseHandler implements ProjectService {
|
||||
return switch (head) {
|
||||
case LIST -> listUserProjects(ex,user.get());
|
||||
case null -> postProject(ex,user.get());
|
||||
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<Long,Map<String,Object>>();
|
||||
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<Project> 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<Long, Project> 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<Long,Map<String,Object>>();
|
||||
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<Long,Map<String,Object>>();
|
||||
var userMap = new HashMap<Long,UmbrellaUser>();
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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<Long, Project> addMembers(HashMap<Long, Project> projects, UserService userService) throws SQLException, UmbrellaException {
|
||||
private Map<Long, Project> addMembers(Map<Long, Project> 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<Long,HashMap<Long, Permission>>();
|
||||
while (rs.next()){
|
||||
var userId = rs.getLong(USER_ID);
|
||||
var projectId = rs.getLong(PROJECT_ID);
|
||||
var permission = Permission.of(rs.getInt(PERMISSIONS));
|
||||
HashMap<Long, Permission> 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<Long, Project> 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<Long, Project> ofCompany(long companyId, boolean includeClosed) throws UmbrellaException {
|
||||
try {
|
||||
var projects = new HashMap<Long,Project>();
|
||||
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<Long, Project> ofUser(long userId, boolean includeClosed, UserService userService) throws UmbrellaException {
|
||||
public Map<Long, Project> ofUser(long userId, boolean includeClosed) throws UmbrellaException {
|
||||
try {
|
||||
var projects = new HashMap<Long,Project>();
|
||||
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());
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user