Browse Source

working on project creation

kanban
Stephan Richter 4 months ago
parent
commit
9626d91ccb
  1. 1
      backend/src/main/java/de/srsoftware/umbrella/backend/Application.java
  2. 36
      company/src/main/java/de/srsoftware/umbrella/company/CompanyModule.java
  3. 2
      core/src/main/java/de/srsoftware/umbrella/core/model/Project.java
  4. 2
      frontend/src/App.svelte
  5. 37
      frontend/src/Components/CompanySelector.svelte
  6. 79
      frontend/src/routes/project/Create.svelte
  7. 4
      frontend/src/routes/project/List.svelte
  8. 13
      frontend/src/routes/project/Settings.svelte
  9. 2
      project/src/main/java/de/srsoftware/umbrella/project/ProjectDb.java
  10. 33
      project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java
  11. 8
      project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java
  12. 10
      translations/src/main/resources/de.json

1
backend/src/main/java/de/srsoftware/umbrella/backend/Application.java

@ -71,6 +71,7 @@ public class Application { @@ -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);

36
company/src/main/java/de/srsoftware/umbrella/company/CompanyModule.java

@ -3,19 +3,28 @@ package de.srsoftware.umbrella.company; @@ -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 { @@ -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> 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<UmbrellaUser> getMembers(long companyId) throws UmbrellaException {
var members = new HashSet<UmbrellaUser>();

2
core/src/main/java/de/srsoftware/umbrella/core/model/Project.java

@ -8,7 +8,7 @@ import java.sql.ResultSet; @@ -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),

2
frontend/src/App.svelte

@ -13,6 +13,7 @@ @@ -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 @@ @@ -46,6 +47,7 @@
<Route path="/document/:id/view" component={ViewDoc} />
<Route path="/message/settings" component={Messages} />
<Route path="/project" component={ProjectList} />
<Route path="/project/add" component={ProjectAdd} />
<Route path="/search" component={Search} />
<Route path="/user" component={User} />
<Route path="/user/create" component={EditUser} />

37
frontend/src/Components/CompanySelector.svelte

@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
<script>
import {onMount} from 'svelte';
import {t} from '../translations.svelte.js';
let { caption, onselect = (company) => console.log('selected '+company.name) } = $props();
let message = t('loading');
let companies = $state(null);
let value = 0;
async function loadCompanies(){
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/company/list`;
var resp = await fetch(url,{ credentials: 'include'});
if (resp.ok){
companies = await resp.json();
} else {
message = await resp.text();
}
}
function select(){
onselect(companies[value]);
}
onMount(loadCompanies)
</script>
{#if companies}
<select onchange={select} bind:value>
<option value={0}>{caption}</option>
{#each companies as company,idx}
<option value={idx}>{company.name}</option>
{/each}
</select>
{:else}
<span>{message}</span>
{/if}

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

@ -2,18 +2,75 @@ @@ -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);
}
</script>
<fieldset>
<legend>
{t('create_new_project')}
</legend>
<style>
label{ display: block }
</style>
{#if error}
<span class="error">{error}</span>
{/if}
<form {onsubmit}>
<fieldset>
<legend>{t('basic_data')}</legend>
<span class="warn">Company Selector</span>
<label>
<input type="text" />
{t('Name')}
</label>
<legend>
{t('create_new_project')}
</legend>
<fieldset>
<legend>{t('basic_data')}</legend>
<label>
<CompanySelector caption={t('no_company')} {onselect} />
{t('company_optional')}
</label>
<label>
<input type="text" bind:value={project.name}/>
{t('Name')}
</label>
<label>
<textarea bind:value={project.description}></textarea>
{t('description')}
</label>
{#if !showSettings}
<button onclick={() => showSettings = true}>{t('extended_settings')}</button>
{/if}
</fieldset>
{#if showSettings}
<Settings bind:settings={project.settings}/>
{/if}
<button type="submit" disabled={!ready}>{t('create')}</button>
</fieldset>
</fieldset>
</form>

4
frontend/src/routes/project/List.svelte

@ -2,11 +2,13 @@ @@ -2,11 +2,13 @@
import { useTinyRouter } from 'svelte-tiny-router';
import { onMount } from 'svelte';
import { t } from '../../translations.svelte.js';
const router = useTinyRouter();
</script>
<fieldset>
<legend>
{t('projects')}
<button>{t('create_new')}</button>
<button onclick={() => router.navigate('/project/add')}>{t('create_new')}</button>
</legend>
</fieldset>

13
frontend/src/routes/project/Settings.svelte

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
<script>
import {t} from '../../translations.svelte.js';
let { settings = $bindable() } = $props();
</script>
<fieldset>
<legend>{t('settings')}</legend>
<label>
<input type="checkbox" bind:checked={settings.show_closed} />
{t('display_closed_tasks')}
</label>
</fieldset>

2
project/src/main/java/de/srsoftware/umbrella/project/ProjectDb.java

@ -7,4 +7,6 @@ import java.util.Collection; @@ -7,4 +7,6 @@ import java.util.Collection;
public interface ProjectDb {
Collection<Project> list(long companyId, boolean includeClosed) throws UmbrellaException;
Project save(Project prj);
}

33
project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java

@ -4,9 +4,9 @@ package de.srsoftware.umbrella.project; @@ -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; @@ -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 { @@ -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 { @@ -61,10 +64,6 @@ public class ProjectModule extends BaseHandler implements ProjectService {
}
}
public Collection<Project> 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 { @@ -77,4 +76,26 @@ public class ProjectModule extends BaseHandler implements ProjectService {
.map(HashMap::new);
return sendContent(ex,items);
}
public Collection<Project> 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);
}
}

8
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) @@ -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;
}
}

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

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

Loading…
Cancel
Save