From 9626d91ccbea966cf43787c126bd31098fbfdcba Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Fri, 18 Jul 2025 15:35:37 +0200 Subject: [PATCH] working on project creation --- .../umbrella/backend/Application.java | 1 + .../umbrella/company/CompanyModule.java | 36 ++++++++- .../umbrella/core/model/Project.java | 2 +- frontend/src/App.svelte | 2 + .../src/Components/CompanySelector.svelte | 37 +++++++++ frontend/src/routes/project/Create.svelte | 79 ++++++++++++++++--- frontend/src/routes/project/List.svelte | 4 +- frontend/src/routes/project/Settings.svelte | 13 +++ .../umbrella/project/ProjectDb.java | 2 + .../umbrella/project/ProjectModule.java | 33 ++++++-- .../srsoftware/umbrella/project/SqliteDb.java | 8 ++ translations/src/main/resources/de.json | 10 +++ 12 files changed, 207 insertions(+), 20 deletions(-) create mode 100644 frontend/src/Components/CompanySelector.svelte create mode 100644 frontend/src/routes/project/Settings.svelte diff --git a/backend/src/main/java/de/srsoftware/umbrella/backend/Application.java b/backend/src/main/java/de/srsoftware/umbrella/backend/Application.java index 6944492..f6afa5b 100644 --- a/backend/src/main/java/de/srsoftware/umbrella/backend/Application.java +++ b/backend/src/main/java/de/srsoftware/umbrella/backend/Application.java @@ -71,6 +71,7 @@ public class Application { var webHandler = new WebHandler(); documentApi .bindPath("/api/document") .on(server); + companyModule .bindPath("/api/company") .on(server); itemApi .bindPath("/api/items") .on(server); markdownApi .bindPath("/api/markdown") .on(server); messageApi .bindPath("/api/messages") .on(server); diff --git a/company/src/main/java/de/srsoftware/umbrella/company/CompanyModule.java b/company/src/main/java/de/srsoftware/umbrella/company/CompanyModule.java index a2aab39..5efff18 100644 --- a/company/src/main/java/de/srsoftware/umbrella/company/CompanyModule.java +++ b/company/src/main/java/de/srsoftware/umbrella/company/CompanyModule.java @@ -3,19 +3,28 @@ package de.srsoftware.umbrella.company; import static de.srsoftware.umbrella.company.Constants.CONFIG_DATABASE; import static de.srsoftware.umbrella.core.ConnectionProvider.connect; +import static de.srsoftware.umbrella.core.Paths.LIST; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException; +import com.sun.net.httpserver.HttpExchange; import de.srsoftware.configuration.Configuration; +import de.srsoftware.tools.Path; +import de.srsoftware.tools.SessionToken; import de.srsoftware.umbrella.company.api.CompanyDb; +import de.srsoftware.umbrella.core.BaseHandler; import de.srsoftware.umbrella.core.api.CompanyService; import de.srsoftware.umbrella.core.api.UserService; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.model.Company; +import de.srsoftware.umbrella.core.model.Token; import de.srsoftware.umbrella.core.model.UmbrellaUser; + +import java.io.IOException; import java.util.Collection; import java.util.HashSet; +import java.util.Optional; -public class CompanyModule implements CompanyService { +public class CompanyModule extends BaseHandler implements CompanyService { private final UserService users; private final CompanyDb companyDb; @@ -26,11 +35,36 @@ public class CompanyModule implements CompanyService { users = userService; } + @Override + public boolean doGet(Path path, HttpExchange ex) throws IOException { + addCors(ex); + try { + Optional token = SessionToken.from(ex).map(Token::of); + var user = users.loadUser(token); + if (user.isEmpty()) return unauthorized(ex); + var head = path.pop(); + return switch (head){ + case LIST -> getCompanyList(user.get(),ex); + case null, + default -> super.doGet(path,ex); + }; + } catch (UmbrellaException e) { + return send(ex,e); + } + } + @Override public Company get(long companyId) throws UmbrellaException { return companyDb.load(companyId); } + private boolean getCompanyList(UmbrellaUser user, HttpExchange ex) throws IOException, UmbrellaException { + var list = listCompaniesOf(user).stream().map(Company::toMap); + return sendContent(ex,list); + } + + + @Override public Collection getMembers(long companyId) throws UmbrellaException { var members = new HashSet(); 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 c0d4020..589b9a2 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 @@ -8,7 +8,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Map; -public record Project(long id, String name, String description, Status status, long companyId, boolean showClosed) implements Mappable { +public record Project(long id, String name, String description, Status status, Long companyId, boolean showClosed) implements Mappable { public enum Status{ Open(10), Started(20), diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 5528c78..30ca8a2 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -13,6 +13,7 @@ import Messages from "./routes/message/Messages.svelte"; import Menu from "./Components/Menu.svelte"; import ProjectList from "./routes/project/List.svelte"; + import ProjectAdd from "./routes/project/Create.svelte"; import ResetPw from "./routes/user/ResetPw.svelte"; import Search from "./routes/search/Search.svelte"; import SendDoc from "./routes/document/Send.svelte"; @@ -46,6 +47,7 @@ + diff --git a/frontend/src/Components/CompanySelector.svelte b/frontend/src/Components/CompanySelector.svelte new file mode 100644 index 0000000..a93f522 --- /dev/null +++ b/frontend/src/Components/CompanySelector.svelte @@ -0,0 +1,37 @@ + + +{#if companies} + +{:else} +{message} +{/if} + diff --git a/frontend/src/routes/project/Create.svelte b/frontend/src/routes/project/Create.svelte index 3541d56..eed12f0 100644 --- a/frontend/src/routes/project/Create.svelte +++ b/frontend/src/routes/project/Create.svelte @@ -2,18 +2,75 @@ import { useTinyRouter } from 'svelte-tiny-router'; import { onMount } from 'svelte'; import { t } from '../../translations.svelte.js'; + + import CompanySelector from '../../Components/CompanySelector.svelte'; + import Settings from './Settings.svelte'; + let showSettings = $state(false); + let ready = $derived(!!project.name.trim()) + let error = $state(null); + + let project = $state({ + name:'', + description:'', + settings:{ + show_closed:false + } + }); + + async function onsubmit(ev){ + ev.preventDefault(); + const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/project`; + var resp = await fetch(url,{ + credentials: 'include', + method: 'POST', + body: JSON.stringify(project) + }); + if (resp.ok){ + router.navigate('/project'); + } else { + error = await resp.text(); + } + } + + function onselect(company){ + project.company_id = company.id; + console.log(project); + } -
- - {t('create_new_project')} - + + +{#if error} +{error} +{/if} +
- {t('basic_data')} - Company Selector - + + {t('create_new_project')} + +
+ {t('basic_data')} + + + + {#if !showSettings} + + {/if} +
+ {#if showSettings} + + {/if} +
-
\ No newline at end of file + \ No newline at end of file diff --git a/frontend/src/routes/project/List.svelte b/frontend/src/routes/project/List.svelte index c877951..9239627 100644 --- a/frontend/src/routes/project/List.svelte +++ b/frontend/src/routes/project/List.svelte @@ -2,11 +2,13 @@ import { useTinyRouter } from 'svelte-tiny-router'; import { onMount } from 'svelte'; import { t } from '../../translations.svelte.js'; + + const router = useTinyRouter();
{t('projects')} - +
\ No newline at end of file diff --git a/frontend/src/routes/project/Settings.svelte b/frontend/src/routes/project/Settings.svelte new file mode 100644 index 0000000..9fc4251 --- /dev/null +++ b/frontend/src/routes/project/Settings.svelte @@ -0,0 +1,13 @@ + +
+ {t('settings')} + +
\ 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 ce7acee..107cbee 100644 --- a/project/src/main/java/de/srsoftware/umbrella/project/ProjectDb.java +++ b/project/src/main/java/de/srsoftware/umbrella/project/ProjectDb.java @@ -7,4 +7,6 @@ import java.util.Collection; public interface ProjectDb { Collection list(long companyId, boolean includeClosed) throws UmbrellaException; + + Project save(Project prj); } 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 b7afd48..038ac0b 100644 --- a/project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java +++ b/project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java @@ -4,9 +4,9 @@ package de.srsoftware.umbrella.project; import static de.srsoftware.umbrella.core.ConnectionProvider.connect; import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.Paths.LIST; -import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.forbidden; -import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException; +import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*; import static de.srsoftware.umbrella.project.Constants.CONFIG_DATABASE; +import static java.lang.Boolean.TRUE; import static java.util.Comparator.comparing; import com.sun.net.httpserver.HttpExchange; @@ -21,6 +21,8 @@ import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.model.Project; import de.srsoftware.umbrella.core.model.Token; import de.srsoftware.umbrella.core.model.UmbrellaUser; +import org.json.JSONObject; + import java.io.IOException; import java.util.Collection; import java.util.HashMap; @@ -54,6 +56,7 @@ public class ProjectModule extends BaseHandler implements ProjectService { var head = path.pop(); return switch (head) { case LIST -> listItems(ex,user.get()); + case null -> postProject(ex,user.get()); default -> super.doGet(path,ex); }; } catch (UmbrellaException e){ @@ -61,10 +64,6 @@ public class ProjectModule extends BaseHandler implements ProjectService { } } - public Collection listProjects(long companyId, boolean includeClosed) throws UmbrellaException { - return projectDb.list(companyId, includeClosed).stream().sorted(comparing(Project::name)).toList(); - } - private boolean listItems(HttpExchange ex, UmbrellaUser user) throws IOException, UmbrellaException { var json = json(ex); if (!(json.has(COMPANY_ID) && json.get(COMPANY_ID) instanceof Number cid)) throw missingFieldException(COMPANY_ID); @@ -77,4 +76,26 @@ public class ProjectModule extends BaseHandler implements ProjectService { .map(HashMap::new); return sendContent(ex,items); } + + public Collection listProjects(long companyId, boolean includeClosed) throws UmbrellaException { + return projectDb.list(companyId, includeClosed).stream().sorted(comparing(Project::name)).toList(); + } + + private boolean postProject(HttpExchange ex, UmbrellaUser user) throws IOException, UmbrellaException { + var json = json(ex); + if (!(json.has(NAME) && json.get(NAME) instanceof String name)) throw missingFieldException(NAME); + var description = json.has(DESCRIPTION) && json.get(DESCRIPTION) instanceof String d ? d : null; + Long companyId = null; + if (json.has(COMPANY_ID) && json.get(COMPANY_ID) instanceof Number number){ + if (!companies.membership(number.longValue(), user.id())) throw forbidden("You are not a member of company {0}!",number); + companyId = number.longValue(); + } + var showClosed = false; + 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); + projectDb.save(prj); + return notFound(ex); + } } \ No newline at end of file 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 b8f04bb..e115641 100644 --- a/project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java +++ b/project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java @@ -138,4 +138,12 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255) throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to load items from database"); } } + + @Override + public Project save(Project prj) { + try { + insertInto(TABLE_PROJECTS); + } + return null; + } } diff --git a/translations/src/main/resources/de.json b/translations/src/main/resources/de.json index d3078aa..6f42c51 100644 --- a/translations/src/main/resources/de.json +++ b/translations/src/main/resources/de.json @@ -6,10 +6,13 @@ "add_position": "hinzufügen", "advertisement" : "Umbrella ist ein Produkt von {0}.", "amount": "Menge", + "bank_account": "Bankverbindung", "base_url": "Basis-URL", + "basic_data": "Basis-Daten", "bookmark": "Lesezeichen", "by": "von", + "client_id": "Client-ID", "client_secret": "Client-Geheimnis", "code": "Code", @@ -17,10 +20,13 @@ "connected_services": "verbundene Login-Services", "confirm_deletion": "Soll '{pos}' wirklich gelöscht werden?", "company": "Firma", + "company_optional": "Firma (optional)", "contact": "Kontakte", "contained_tax": "enthaltene Steuer", "content": "Inhalt", + "create": "anlegen", "create_new_document": "neues Dokument", + "create_new_project": "neues Projekt anlegen", "create_new_user": "Neuen Benutzer anlegen", "CREATE_USERS": "Nutzer anlegen", "create_pdf": "PDF erzeugen", @@ -35,10 +41,12 @@ "DELETE_USERS": "Nutzer löschen", "delivery_date": "Lieferdatum", "description": "Beschreibung", + "display_closed_tasks": "abgeschlossene Aufgaben anzeigen", "document": "Dokumente", "documents": "Dokumente", "do_login" : "anmelden", "do_send" : "versenden", + "edit": "Bearbeiten", "editing": "Nutzer {0} bearbeiten", "edit_password": "Passwort ändern", @@ -47,6 +55,7 @@ "email_or_username": "Email oder Nutzername", "estimated_time": "geschätzte Zeit", "estimated_times": "geschätzte Zeiten", + "extended_settings": "erweiterte Einstellungen", "failed": "fehlgeschlagen", "files": "Dateien", @@ -93,6 +102,7 @@ "net_sum": "Netto-Summe", "new_password": "neues Passwort", "new_document_from": "{2} / neues {0}s-Dokument von {1}", + "no_company": "keine Firma", "notes": "Notizen", "number": "Nummer",