implemented function to return estimated times of company.
Therefore task and project module had to be created and partially implemented
This commit is contained in:
@@ -19,14 +19,13 @@ dependencies{
|
||||
implementation(project(":legacy"))
|
||||
implementation(project(":markdown"))
|
||||
implementation(project(":messages"))
|
||||
implementation(project(":task"))
|
||||
implementation(project(":project"))
|
||||
implementation(project(":translations"))
|
||||
implementation(project(":user"))
|
||||
implementation(project(":web"))
|
||||
implementation("de.srsoftware:configuration.api:1.0.2")
|
||||
implementation("de.srsoftware:configuration.json:1.0.3")
|
||||
implementation("de.srsoftware:tools.optionals:1.0.0")
|
||||
implementation("de.srsoftware:tools.slf4j2syslog:1.0.1") // this provides a slf4j implementation that forwards to System.Logger
|
||||
implementation("de.srsoftware:tools.util:2.0.3")
|
||||
}
|
||||
|
||||
tasks.jar {
|
||||
|
||||
@@ -17,6 +17,8 @@ import de.srsoftware.umbrella.legacy.LegacyApi;
|
||||
import de.srsoftware.umbrella.markdown.MarkdownApi;
|
||||
import de.srsoftware.umbrella.message.MessageApi;
|
||||
import de.srsoftware.umbrella.message.MessageSystem;
|
||||
import de.srsoftware.umbrella.project.ProjectModule;
|
||||
import de.srsoftware.umbrella.task.TaskModule;
|
||||
import de.srsoftware.umbrella.translations.Translations;
|
||||
import de.srsoftware.umbrella.user.UserModule;
|
||||
import de.srsoftware.umbrella.web.WebHandler;
|
||||
@@ -58,16 +60,20 @@ public class Application {
|
||||
var userModule = new UserModule(config,messageSystem);
|
||||
var companyModule = new CompanyModule(config, userModule);
|
||||
var documentApi = new DocumentApi(companyModule, config);
|
||||
var itemApi = new ItemApi(config,userModule);
|
||||
var itemApi = new ItemApi(config,companyModule);
|
||||
var legacyApi = new LegacyApi(userModule.userDb(),config);
|
||||
var markdownApi = new MarkdownApi(userModule);
|
||||
var messageApi = new MessageApi(messageSystem);
|
||||
var projectModule = new ProjectModule(config,companyModule);
|
||||
var taskModule = new TaskModule(config,projectModule);
|
||||
var webHandler = new WebHandler();
|
||||
|
||||
documentApi .bindPath("/api/document") .on(server);
|
||||
itemApi .bindPath("/api/items") .on(server);
|
||||
markdownApi .bindPath("/api/markdown") .on(server);
|
||||
messageApi .bindPath("/api/messages") .on(server);
|
||||
projectModule .bindPath("/api/project") .on(server);
|
||||
taskModule .bindPath("/api/task") .on(server);
|
||||
translationModule.bindPath("/api/translations").on(server);
|
||||
userModule .bindPath("/api/user") .on(server);
|
||||
legacyApi .bindPath("/legacy") .on(server);
|
||||
|
||||
@@ -40,8 +40,12 @@ subprojects {
|
||||
dependencies {
|
||||
testImplementation(platform("org.junit:junit-bom:5.10.0"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
implementation("de.srsoftware:configuration.api:1.0.2")
|
||||
implementation("de.srsoftware:tools.jdbc:1.3.2")
|
||||
implementation("de.srsoftware:tools.http:6.0.4")
|
||||
implementation("de.srsoftware:tools.logging:1.3.2")
|
||||
implementation("de.srsoftware:tools.optionals:1.0.0")
|
||||
implementation("de.srsoftware:tools.util:2.0.3")
|
||||
implementation("org.json:json:20240303")
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,5 @@ description = "Umbrella : Companies"
|
||||
|
||||
dependencies{
|
||||
implementation(project(":core"))
|
||||
implementation("de.srsoftware:configuration.api:1.0.2")
|
||||
implementation("de.srsoftware:tools.jdbc:1.3.2")
|
||||
implementation("de.srsoftware:tools.util:2.0.3")
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,4 @@ description = "Umbrella : Documents"
|
||||
|
||||
dependencies{
|
||||
implementation(project(":core"))
|
||||
implementation("de.srsoftware:configuration.api:1.0.2")
|
||||
implementation("de.srsoftware:tools.jdbc:1.3.2")
|
||||
implementation("de.srsoftware:tools.util:2.0.3")
|
||||
}
|
||||
@@ -11,8 +11,6 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
implementation("de.srsoftware:tools.mime:1.1.2")
|
||||
implementation("de.srsoftware:tools.optionals:1.0.0")
|
||||
implementation("de.srsoftware:tools.util:2.0.3")
|
||||
implementation("org.xerial:sqlite-jdbc:3.49.0.0")
|
||||
testImplementation(platform("org.junit:junit-bom:5.10.0"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
|
||||
@@ -48,12 +48,13 @@ public class Constants {
|
||||
public static final String RENDERED = "rendered";
|
||||
public static final String SENDER = "sender";
|
||||
public static final String SETTINGS = "settings";
|
||||
public static final String SHOW_CLOSED = "show_closed";
|
||||
public static final String SOURCE = "source";
|
||||
public static final String STATE = "state";
|
||||
public static final String STATUS = "status";
|
||||
public static final String STATUS_CODE = "code";
|
||||
public static final String STRING = "string";
|
||||
public static final String SUBJECT = "subject";
|
||||
|
||||
public static final String TABLE_SETTINGS = "settings";
|
||||
|
||||
public static final String TEMPLATE = "template";
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/* © SRSoftware 2025 */
|
||||
package de.srsoftware.umbrella.core.api;
|
||||
|
||||
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
||||
import de.srsoftware.umbrella.core.model.Project;
|
||||
import java.util.Collection;
|
||||
|
||||
public interface ProjectService {
|
||||
public Collection<Project> listProjects(long companyId,boolean includeClosed) throws UmbrellaException;
|
||||
|
||||
CompanyService companyService();
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/* © SRSoftware 2025 */
|
||||
package de.srsoftware.umbrella.core.model;
|
||||
|
||||
import static de.srsoftware.umbrella.core.Constants.*;
|
||||
|
||||
import de.srsoftware.tools.Mappable;
|
||||
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 enum Status{
|
||||
Open(10),
|
||||
Started(20),
|
||||
Pending(40),
|
||||
Complete(60),
|
||||
Cancelled(100);
|
||||
|
||||
private int code;
|
||||
|
||||
Status(int code){
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int code(){
|
||||
return code;
|
||||
}
|
||||
|
||||
public static Status of(int code){
|
||||
return switch (code){
|
||||
case 10 -> Open;
|
||||
case 20 -> Started;
|
||||
case 40 -> Pending;
|
||||
case 60 -> Complete;
|
||||
case 100 -> Cancelled;
|
||||
default -> throw new IllegalArgumentException();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static Project of(ResultSet rs) throws SQLException {
|
||||
return new Project(rs.getLong(ID),rs.getString(NAME),rs.getString(DESCRIPTION),Status.of(rs.getInt(STATUS)),rs.getLong(COMPANY_ID),rs.getBoolean(SHOW_CLOSED));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> toMap() {
|
||||
return Map.of(
|
||||
ID,id,
|
||||
NAME,name,
|
||||
DESCRIPTION,description,
|
||||
STATUS,Map.of(STATUS_CODE,status.code(), NAME,status.name()),
|
||||
COMPANY_ID,companyId,
|
||||
SHOW_CLOSED,showClosed
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,5 @@ description = "Umbrella : Documents"
|
||||
dependencies{
|
||||
implementation(project(":company"))
|
||||
implementation(project(":core"))
|
||||
implementation("de.srsoftware:configuration.api:1.0.2")
|
||||
implementation("de.srsoftware:tools.jdbc:1.3.2")
|
||||
implementation("de.srsoftware:tools.mime:1.1.2")
|
||||
implementation("de.srsoftware:tools.optionals:1.0.0")
|
||||
implementation("de.srsoftware:tools.util:2.0.3")
|
||||
}
|
||||
@@ -9,11 +9,10 @@ import static de.srsoftware.umbrella.documents.Constants.FIELD_CUSTOMER;
|
||||
import static java.util.Optional.empty;
|
||||
|
||||
import de.srsoftware.tools.Mappable;
|
||||
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
|
||||
|
||||
11
frontend/src/Components/Item.svelte
Normal file
11
frontend/src/Components/Item.svelte
Normal file
@@ -0,0 +1,11 @@
|
||||
<script>
|
||||
import { t } from '../translations.svelte.js';
|
||||
|
||||
let { item, onclick } = $props();
|
||||
</script>
|
||||
|
||||
<fieldset {onclick}>
|
||||
<legend>{item.code} | {item.name}</legend>
|
||||
<div>{@html item.description.rendered}</div>
|
||||
<span>{item.unit_price/100} {item.currency} / {item.unit}</span>
|
||||
</fieldset>
|
||||
@@ -1,7 +1,39 @@
|
||||
<script>
|
||||
import { t } from '../../translations.svelte.js';
|
||||
import { onMount } from 'svelte';
|
||||
import EstimatedTime from '../../Components/Item.svelte';
|
||||
let { company_id, onSelect = (item) => {} } = $props();
|
||||
|
||||
let items = $state(null);
|
||||
let error = $state(null);
|
||||
|
||||
async function loadItems(){
|
||||
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/task/estimated_times`;
|
||||
let data = { company_id: company_id };
|
||||
const resp = await fetch(url,{
|
||||
credentials:'include',
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
if (resp.ok){
|
||||
items = await resp.json();
|
||||
} else {
|
||||
error = await resp.body();
|
||||
}
|
||||
}
|
||||
|
||||
onMount(loadItems);
|
||||
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<h1>Estimated Times</h1>
|
||||
<h1>{t('task.estimated_times')}</h1>
|
||||
{#if error}
|
||||
<span class="error">{error}</span>
|
||||
{/if}
|
||||
{#if items}
|
||||
{#each items as item,id}
|
||||
<EstimatedTime item={item} onclick={() => onSelect(item)} />
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,12 +1,21 @@
|
||||
<script>
|
||||
import { t } from '../../translations.svelte.js';
|
||||
import { onMount } from 'svelte';
|
||||
import Item from '../../Components/Item.svelte';
|
||||
let { company_id, onSelect = (item) => {} } = $props();
|
||||
|
||||
let items = $state(null);
|
||||
let error = $state(null);
|
||||
|
||||
|
||||
async function loadItems(){
|
||||
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/items/list`;
|
||||
const resp = await fetch(url,{credentials:'include'});
|
||||
let data = { company_id: company_id };
|
||||
const resp = await fetch(url,{
|
||||
credentials:'include',
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
if (resp.ok){
|
||||
items = await resp.json();
|
||||
} else {
|
||||
@@ -19,8 +28,13 @@
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<h1>Items</h1>
|
||||
<h1>{t('items.items')}</h1>
|
||||
{#if error}
|
||||
<span class="error">{error}</span>
|
||||
{/if}
|
||||
{#if items}
|
||||
{#each items as item,id}
|
||||
<Item item={item} onclick={() => onSelect(item)} />
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
@@ -4,20 +4,36 @@
|
||||
import ItemList from './ItemList.svelte';
|
||||
import TimeList from './TimeList.svelte';
|
||||
|
||||
let { close = () => {}, doc = $bindable({}) } = $props();
|
||||
let { close = () => {}, doc = $bindable({}), onSelect = (item) => {} } = $props();
|
||||
|
||||
let select = $state(0);
|
||||
|
||||
function estimateSelected(estimate){
|
||||
onSelect({estimate:estimate});
|
||||
close();
|
||||
}
|
||||
|
||||
function itemSelected(item){
|
||||
onSelect({item:item});
|
||||
close();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
div{
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
bottom: 20px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(0,0,0,0.8);
|
||||
background: rgba(0,0,0,0.9);
|
||||
padding: 10px;
|
||||
overflow: auto;
|
||||
|
||||
}
|
||||
span{
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -29,9 +45,9 @@
|
||||
<button onclick={close}>{t('document.abort')}</button>
|
||||
</span>
|
||||
{#if select == 0}
|
||||
<ItemList />
|
||||
<ItemList company_id={doc.company.id} onSelect={itemSelected} />
|
||||
{:else if select == 1}
|
||||
<EstimateList />
|
||||
<EstimateList company_id={doc.company.id} onSelect={estimateSelected} />
|
||||
{:else}
|
||||
<TimeList />
|
||||
{/if}
|
||||
|
||||
@@ -63,6 +63,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
function addPosition(selected){
|
||||
let newPos = {};
|
||||
if (selected.item) newPos['item']=selected.item.id;
|
||||
console.log(JSON.stringify({newPos:newPos}));
|
||||
}
|
||||
|
||||
onMount(loadDoc);
|
||||
</script>
|
||||
|
||||
@@ -186,5 +192,5 @@
|
||||
{/if}
|
||||
|
||||
{#if position_select}
|
||||
<PositionSelector close={() => position_select=false} {doc} />
|
||||
<PositionSelector close={() => position_select=false} {doc} onSelect={addPosition} />
|
||||
{/if}
|
||||
@@ -2,7 +2,4 @@ description = "Umbrella : Items"
|
||||
|
||||
dependencies{
|
||||
implementation(project(":core"))
|
||||
implementation("de.srsoftware:configuration.api:1.0.2")
|
||||
implementation("de.srsoftware:tools.jdbc:1.3.2")
|
||||
implementation("de.srsoftware:tools.util:2.0.3")
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
/* © SRSoftware 2025 */
|
||||
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";
|
||||
public static final String UNIT = "unit";
|
||||
public static final String UNIT_PRICE = "unit_price";
|
||||
}
|
||||
|
||||
@@ -1,4 +1,38 @@
|
||||
/* © SRSoftware 2025 */
|
||||
package de.srsoftware.umbrella.items;
|
||||
|
||||
public class Item {
|
||||
import static de.srsoftware.umbrella.core.Constants.*;
|
||||
import static de.srsoftware.umbrella.core.Util.markdown;
|
||||
import static de.srsoftware.umbrella.items.Constants.*;
|
||||
|
||||
import de.srsoftware.tools.Mappable;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
|
||||
public record Item(long id, long companyId, String code, String name, String description, String unit, long unitPrice, long tax) implements Mappable {
|
||||
public static Item of(ResultSet rs) throws SQLException {
|
||||
var id = rs.getLong(ID);
|
||||
var companyId = rs.getLong(COMPANY_ID);
|
||||
var code = rs.getString(CODE);
|
||||
var name = rs.getString(NAME);
|
||||
var desc = rs.getString(DESCRIPTION);
|
||||
var unit = rs.getString(UNIT);
|
||||
var unitPrice = rs.getLong(UNIT_PRICE);
|
||||
var tax = rs.getInt(TAX);
|
||||
|
||||
return new Item(id,companyId,code,name,desc,unit,unitPrice,tax);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> toMap() {
|
||||
return Map.of(
|
||||
ID,id,
|
||||
COMPANY_ID,companyId,
|
||||
CODE,code,NAME,name,
|
||||
DESCRIPTION,Map.of(SOURCE,description,RENDERED,markdown(description)),
|
||||
UNIT,unit,
|
||||
UNIT_PRICE,unitPrice,
|
||||
TAX,tax);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,42 @@
|
||||
/* © SRSoftware 2025 */
|
||||
package de.srsoftware.umbrella.items;
|
||||
|
||||
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.items.Constants.CONFIG_DATABASE;
|
||||
|
||||
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.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.Token;
|
||||
import de.srsoftware.umbrella.core.model.UmbrellaUser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Optional;
|
||||
|
||||
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 static de.srsoftware.umbrella.items.Constants.CONFIG_DATABASE;
|
||||
|
||||
public class ItemApi extends BaseHandler {
|
||||
|
||||
private final ItemDb itemDb;
|
||||
private final CompanyService companies;
|
||||
private final UserService users;
|
||||
|
||||
public ItemApi(Configuration config, UserService userService) throws UmbrellaException {
|
||||
public ItemApi(Configuration config, CompanyService companyService) throws UmbrellaException {
|
||||
var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingFieldException(CONFIG_DATABASE));
|
||||
itemDb = new SqliteDb(connect(dbFile));
|
||||
users = userService;
|
||||
companies = companyService;
|
||||
users = companies.userService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doGet(Path path, HttpExchange ex) throws IOException {
|
||||
public boolean doPost(Path path, HttpExchange ex) throws IOException {
|
||||
addCors(ex);
|
||||
try {
|
||||
Optional<Token> token = SessionToken.from(ex).map(Token::of);
|
||||
@@ -38,7 +44,7 @@ public class ItemApi extends BaseHandler {
|
||||
if (user.isEmpty()) return unauthorized(ex);
|
||||
var head = path.pop();
|
||||
return switch (head) {
|
||||
case LIST -> listItems(ex,user);
|
||||
case LIST -> listItems(ex,user.get());
|
||||
default -> super.doGet(path,ex);
|
||||
};
|
||||
} catch (UmbrellaException e){
|
||||
@@ -46,8 +52,17 @@ public class ItemApi extends BaseHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean listItems(HttpExchange ex, Optional<UmbrellaUser> user) throws IOException {
|
||||
var items = itemDb.list();
|
||||
return notImplemented(ex,"listItems",this);
|
||||
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);
|
||||
var companyId = cid.longValue();
|
||||
var company = companies.get(companyId);
|
||||
if (!companies.membership(companyId,user.id())) throw forbidden("You are mot a member of company {0}",company.name());
|
||||
var items = itemDb.list(companyId)
|
||||
.stream()
|
||||
.map(Item::toMap)
|
||||
.map(HashMap::new)
|
||||
.peek(map -> map.put(FIELD_CURRENCY,company.currency()));
|
||||
return sendContent(ex,items);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
/* © SRSoftware 2025 */
|
||||
package de.srsoftware.umbrella.items;
|
||||
|
||||
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
||||
import java.util.Collection;
|
||||
|
||||
public interface ItemDb {
|
||||
Collection<Item> list();
|
||||
Collection<Item> list(long companyId) throws UmbrellaException;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,36 @@
|
||||
/* © SRSoftware 2025 */
|
||||
package de.srsoftware.umbrella.items;
|
||||
|
||||
import static de.srsoftware.tools.jdbc.Condition.equal;
|
||||
import static de.srsoftware.tools.jdbc.Query.select;
|
||||
import static de.srsoftware.umbrella.core.Constants.COMPANY_ID;
|
||||
import static de.srsoftware.umbrella.core.ResponseCode.HTTP_SERVER_ERROR;
|
||||
import static de.srsoftware.umbrella.items.Constants.TABLE_ITEMS;
|
||||
|
||||
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
public class SqliteDb implements ItemDb{
|
||||
|
||||
private final Connection db;
|
||||
|
||||
public SqliteDb(Connection connection) {
|
||||
db = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Item> list(long companyId) throws UmbrellaException {
|
||||
try {
|
||||
var items = new HashSet<Item>();
|
||||
var rs = select("*").from(TABLE_ITEMS).where(COMPANY_ID, equal(companyId)).exec(db);
|
||||
while (rs.next()) items.add(Item.of(rs));
|
||||
rs.close();
|
||||
return items;
|
||||
} catch (SQLException e) {
|
||||
throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to load items from database");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,7 @@ description = "Umbrella : Legacy API"
|
||||
dependencies{
|
||||
implementation(project(":core"))
|
||||
implementation(project(":user"))
|
||||
implementation("de.srsoftware:configuration.api:1.0.2")
|
||||
implementation("de.srsoftware:tools.jdbc:1.3.2")
|
||||
implementation("de.srsoftware:tools.mime:1.1.2")
|
||||
implementation("de.srsoftware:tools.optionals:1.0.0")
|
||||
implementation("de.srsoftware:tools.util:2.0.3")
|
||||
implementation("org.bitbucket.b_c:jose4j:0.9.6")
|
||||
implementation("org.xerial:sqlite-jdbc:3.49.0.0")
|
||||
}
|
||||
@@ -3,11 +3,7 @@ description = "Umbrella : Message subsystem"
|
||||
dependencies{
|
||||
implementation(project(":core"))
|
||||
implementation("com.sun.mail:jakarta.mail:2.0.1")
|
||||
implementation("de.srsoftware:configuration.api:1.0.2")
|
||||
implementation("de.srsoftware:tools.jdbc:1.3.2")
|
||||
implementation("de.srsoftware:tools.mime:1.1.2")
|
||||
implementation("de.srsoftware:tools.optionals:1.0.0")
|
||||
implementation("de.srsoftware:tools.util:2.0.3")
|
||||
implementation("org.bitbucket.b_c:jose4j:0.9.6")
|
||||
implementation("org.xerial:sqlite-jdbc:3.49.0.0")
|
||||
}
|
||||
5
project/build.gradle.kts
Normal file
5
project/build.gradle.kts
Normal file
@@ -0,0 +1,5 @@
|
||||
description = "Umbrella : Projects"
|
||||
|
||||
dependencies{
|
||||
implementation(project(":core"))
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/* © SRSoftware 2025 */
|
||||
package de.srsoftware.umbrella.project;
|
||||
|
||||
public class Constants {
|
||||
private Constants(){}
|
||||
public static final String CONFIG_DATABASE = "umbrella.modules.project.database";
|
||||
public static final String TABLE_PROJECTS = "projects";
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/* © SRSoftware 2025 */
|
||||
package de.srsoftware.umbrella.project;
|
||||
|
||||
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
||||
import de.srsoftware.umbrella.core.model.Project;
|
||||
import java.util.Collection;
|
||||
|
||||
public interface ProjectDb {
|
||||
Collection<Project> list(long companyId, boolean includeClosed) throws UmbrellaException;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/* © SRSoftware 2025 */
|
||||
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.project.Constants.CONFIG_DATABASE;
|
||||
|
||||
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.core.BaseHandler;
|
||||
import de.srsoftware.umbrella.core.api.CompanyService;
|
||||
import de.srsoftware.umbrella.core.api.ProjectService;
|
||||
import de.srsoftware.umbrella.core.api.UserService;
|
||||
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 java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ProjectModule extends BaseHandler implements ProjectService {
|
||||
|
||||
private final ProjectDb projectDb;
|
||||
private final CompanyService companies;
|
||||
private final UserService users;
|
||||
|
||||
public ProjectModule(Configuration config, CompanyService companyService) throws UmbrellaException {
|
||||
var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingFieldException(CONFIG_DATABASE));
|
||||
projectDb = new SqliteDb(connect(dbFile));
|
||||
companies = companyService;
|
||||
users = companies.userService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompanyService companyService() {
|
||||
return companies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doPost(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 -> listItems(ex,user.get());
|
||||
default -> super.doGet(path,ex);
|
||||
};
|
||||
} catch (UmbrellaException e){
|
||||
return send(ex,e);
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<Project> listProjects(long companyId, boolean includeClosed) throws UmbrellaException {
|
||||
return projectDb.list(companyId, includeClosed);
|
||||
}
|
||||
|
||||
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);
|
||||
var companyId = cid.longValue();
|
||||
var company = companies.get(companyId);
|
||||
if (!companies.membership(companyId,user.id())) throw forbidden("You are mot a member of company {0}",company.name());
|
||||
var items = listProjects(companyId,false)
|
||||
.stream()
|
||||
.map(Project::toMap)
|
||||
.map(HashMap::new);
|
||||
return sendContent(ex,items);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/* © SRSoftware 2025 */
|
||||
package de.srsoftware.umbrella.project;
|
||||
|
||||
import static de.srsoftware.tools.jdbc.Condition.equal;
|
||||
import static de.srsoftware.tools.jdbc.Condition.lessThan;
|
||||
import static de.srsoftware.tools.jdbc.Query.select;
|
||||
import static de.srsoftware.umbrella.core.Constants.COMPANY_ID;
|
||||
import static de.srsoftware.umbrella.core.Constants.STATUS;
|
||||
import static de.srsoftware.umbrella.core.ResponseCode.HTTP_SERVER_ERROR;
|
||||
import static de.srsoftware.umbrella.project.Constants.TABLE_PROJECTS;
|
||||
|
||||
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
||||
import de.srsoftware.umbrella.core.model.Project;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
public class SqliteDb implements ProjectDb {
|
||||
|
||||
private final Connection db;
|
||||
|
||||
public SqliteDb(Connection connection) {
|
||||
db = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Project> list(long companyId, boolean includeClosed) throws UmbrellaException {
|
||||
try {
|
||||
var items = new HashSet<Project>();
|
||||
var query = select("*").from(TABLE_PROJECTS).where(COMPANY_ID, equal(companyId));
|
||||
if (!includeClosed) query = query.where(STATUS,lessThan(Project.Status.Complete.code()));
|
||||
var rs = query.exec(db);
|
||||
while (rs.next()) items.add(Project.of(rs));
|
||||
rs.close();
|
||||
return items;
|
||||
} catch (SQLException e) {
|
||||
throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to load items from database");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,4 +13,6 @@ include("company")
|
||||
include("contact")
|
||||
|
||||
include("markdown")
|
||||
include("items")
|
||||
include("project")
|
||||
include("items")
|
||||
include("task")
|
||||
|
||||
6
task/build.gradle.kts
Normal file
6
task/build.gradle.kts
Normal file
@@ -0,0 +1,6 @@
|
||||
description = "Umbrella : Tasks"
|
||||
|
||||
dependencies{
|
||||
implementation(project(":core"))
|
||||
implementation(project(":project"))
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/* © SRSoftware 2025 */
|
||||
package de.srsoftware.umbrella.task;
|
||||
|
||||
public class Constants {
|
||||
private Constants(){}
|
||||
|
||||
public static final String CONFIG_DATABASE = "umbrella.modules.task.database";
|
||||
public static final String CHILDREN = "children";
|
||||
public static final String DUE_DATE = "due_date";
|
||||
public static final String ESTIMATED_TIMES = "estimated_times";
|
||||
public static final String ESTIMATED_TIME = "estimated_time";
|
||||
public static final String EST_TIME = "est_time";
|
||||
public static final String NO_INDEX = "no_index";
|
||||
public static final String PARENT_TASK_ID = "parent_task_id";
|
||||
public static final String PROJECT_ID = "project_id";
|
||||
public static final String START_DATE = "start_date";
|
||||
public static final String TABLE_TASKS = "tasks";
|
||||
public static final String FIELD_TASKS = "tasks";
|
||||
}
|
||||
38
task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java
Normal file
38
task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java
Normal file
@@ -0,0 +1,38 @@
|
||||
/* © SRSoftware 2025 */
|
||||
package de.srsoftware.umbrella.task;
|
||||
|
||||
|
||||
import static de.srsoftware.tools.jdbc.Condition.in;
|
||||
import static de.srsoftware.tools.jdbc.Query.select;
|
||||
import static de.srsoftware.umbrella.core.ResponseCode.HTTP_SERVER_ERROR;
|
||||
import static de.srsoftware.umbrella.task.Constants.PROJECT_ID;
|
||||
import static de.srsoftware.umbrella.task.Constants.TABLE_TASKS;
|
||||
|
||||
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class SqliteDb implements TaskDb {
|
||||
|
||||
private final Connection db;
|
||||
|
||||
public SqliteDb(Connection connection) {
|
||||
db = connection;
|
||||
}
|
||||
|
||||
public Collection<Task> listTasks(List<Long> projectIds) throws UmbrellaException {
|
||||
try {
|
||||
var rs = select("*").from(TABLE_TASKS).where(PROJECT_ID, in(projectIds.toArray())).exec(db);
|
||||
var list = new HashSet<Task>();
|
||||
while (rs.next()) list.add(Task.of(rs));
|
||||
rs.close();
|
||||
return list;
|
||||
} catch (SQLException e) {
|
||||
throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to load tasks for project ids");
|
||||
}
|
||||
}
|
||||
}
|
||||
54
task/src/main/java/de/srsoftware/umbrella/task/Task.java
Normal file
54
task/src/main/java/de/srsoftware/umbrella/task/Task.java
Normal file
@@ -0,0 +1,54 @@
|
||||
/* © SRSoftware 2025 */
|
||||
package de.srsoftware.umbrella.task;
|
||||
|
||||
import static de.srsoftware.tools.Optionals.nullIfEmpty;
|
||||
import static de.srsoftware.umbrella.core.Constants.*;
|
||||
import static de.srsoftware.umbrella.core.Constants.SHOW_CLOSED;
|
||||
import static de.srsoftware.umbrella.core.Constants.STATUS;
|
||||
import static de.srsoftware.umbrella.task.Constants.*;
|
||||
|
||||
import de.srsoftware.tools.Mappable;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public record Task(long id, long projectId, Long parentTaskId, String name, String description, int status, Double estimatedTime, LocalDate start, LocalDate dueDate,boolean showClosed, boolean noIndex) implements Mappable {
|
||||
public static Task of(ResultSet rs) throws SQLException {
|
||||
var estTime = rs.getDouble(EST_TIME);
|
||||
var parentTaskId = rs.getLong(PARENT_TASK_ID);
|
||||
var startDate = nullIfEmpty(rs.getString(START_DATE));
|
||||
var dueDate = nullIfEmpty(rs.getString(DUE_DATE));
|
||||
return new Task(
|
||||
rs.getLong(ID),
|
||||
rs.getLong(PROJECT_ID),
|
||||
parentTaskId == 0d ? null : parentTaskId,
|
||||
rs.getString(NAME),
|
||||
rs.getString(DESCRIPTION),
|
||||
rs.getInt(STATUS),
|
||||
estTime == 0d ? null : estTime,
|
||||
startDate != null ? LocalDate.parse(startDate) : null,
|
||||
dueDate != null ? LocalDate.parse(dueDate) : null,
|
||||
rs.getBoolean(SHOW_CLOSED),
|
||||
rs.getBoolean(NO_INDEX)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> toMap() {
|
||||
var map = new HashMap<String,Object>();
|
||||
map.put(ID, id);
|
||||
map.put(PROJECT_ID, projectId);
|
||||
map.put(PARENT_TASK_ID, parentTaskId);
|
||||
map.put(NAME, name);
|
||||
map.put(DESCRIPTION, description);
|
||||
map.put(STATUS, status);
|
||||
map.put(ESTIMATED_TIME, estimatedTime);
|
||||
map.put(START_DATE,start);
|
||||
map.put(DUE_DATE,dueDate);
|
||||
map.put(SHOW_CLOSED,showClosed);
|
||||
map.put(NO_INDEX,noIndex);
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/* © SRSoftware 2025 */
|
||||
package de.srsoftware.umbrella.task;
|
||||
|
||||
|
||||
|
||||
public interface TaskDb {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/* © SRSoftware 2025 */
|
||||
package de.srsoftware.umbrella.task;
|
||||
|
||||
import static de.srsoftware.tools.Optionals.is0;
|
||||
import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
|
||||
import static de.srsoftware.umbrella.core.Constants.COMPANY_ID;
|
||||
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.forbidden;
|
||||
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException;
|
||||
import static de.srsoftware.umbrella.task.Constants.*;
|
||||
import static java.util.Objects.isNull;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
|
||||
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.core.BaseHandler;
|
||||
import de.srsoftware.umbrella.core.api.CompanyService;
|
||||
import de.srsoftware.umbrella.core.api.ProjectService;
|
||||
import de.srsoftware.umbrella.core.api.UserService;
|
||||
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 java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class TaskModule extends BaseHandler {
|
||||
|
||||
private final SqliteDb taskDb;
|
||||
private final ProjectService projects;
|
||||
private final UserService users;
|
||||
private final CompanyService companies;
|
||||
|
||||
public TaskModule(Configuration config, ProjectService projectService) throws UmbrellaException {
|
||||
var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingFieldException(CONFIG_DATABASE));
|
||||
taskDb = new SqliteDb(connect(dbFile));
|
||||
projects = projectService;
|
||||
companies = projectService.companyService();
|
||||
users = companies.userService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doPost(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 ESTIMATED_TIMES -> estimatedTimes(user.get(),ex);
|
||||
default -> super.doGet(path,ex);
|
||||
};
|
||||
} catch (UmbrellaException e){
|
||||
return send(ex,e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean estimatedTimes(UmbrellaUser user, HttpExchange ex) throws IOException, UmbrellaException {
|
||||
var json = json(ex);
|
||||
if (!(json.has(COMPANY_ID) && json.get(COMPANY_ID) instanceof Number cid)) throw missingFieldException(COMPANY_ID);
|
||||
var companyId = cid.longValue();
|
||||
var company = companies.get(companyId);
|
||||
if (!companies.membership(companyId,user.id())) throw forbidden("You are mot a member of company {0}",company.name());
|
||||
var projects = this.projects.listProjects(companyId,false);
|
||||
var taskList = taskDb.listTasks(projects.stream().map(Project::id).toList());
|
||||
var map = taskList.stream().collect(toMap(Task::id, t -> t));
|
||||
var tree = new HashMap<Long,Map<String,Object>>();
|
||||
taskList.stream().filter(task -> !is0(task.estimatedTime())).forEach(task -> placeInTree(task,tree,map));
|
||||
var result = new ArrayList<Map<String,Object>>();
|
||||
projects.forEach(project -> {
|
||||
var projectMap = new HashMap<>(project.toMap());
|
||||
var children = tree.values().stream().filter(root -> project.id() == (Long)root.get(PROJECT_ID)).toList();
|
||||
projectMap.put(FIELD_TASKS,children);
|
||||
result.add(projectMap);
|
||||
});
|
||||
return sendContent(ex,result);
|
||||
}
|
||||
|
||||
private Map<String,Object> placeInTree(Task task, HashMap<Long, Map<String,Object>> tree, Map<Long, Task> map) {
|
||||
var taskMap = task.toMap();
|
||||
if (task.parentTaskId() != null){
|
||||
Task parent = map.get(task.parentTaskId());
|
||||
var trunk = placeInTree(parent,tree,map);
|
||||
ArrayList<Object> children = (ArrayList<Object>) trunk.computeIfAbsent(CHILDREN, k -> new ArrayList<Object>());
|
||||
children.add(taskMap);
|
||||
return taskMap;
|
||||
}
|
||||
tree.put(task.id(),taskMap);
|
||||
return taskMap;
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,9 @@
|
||||
"home" : {
|
||||
"Welcome" : "Willkommen, {0}"
|
||||
},
|
||||
"items": {
|
||||
"items": "Artikel"
|
||||
},
|
||||
"login" : {
|
||||
"do_login" : "anmelden",
|
||||
"Email_or_Username": "Email oder Nutzername",
|
||||
@@ -91,6 +94,9 @@
|
||||
"404": "Seite nicht gefunden",
|
||||
"501": "Nicht implementiert"
|
||||
},
|
||||
"task": {
|
||||
"estimated_times": "geschätzte Zeiten"
|
||||
},
|
||||
"user" : {
|
||||
"actions": "Aktionen",
|
||||
"abort": "abbrechen",
|
||||
|
||||
@@ -3,11 +3,7 @@ description = "Umbrella : User"
|
||||
dependencies{
|
||||
implementation(project(":core"))
|
||||
implementation(project(":messages"))
|
||||
implementation("de.srsoftware:configuration.api:1.0.2")
|
||||
implementation("de.srsoftware:tools.jdbc:1.3.2")
|
||||
implementation("de.srsoftware:tools.mime:1.1.2")
|
||||
implementation("de.srsoftware:tools.optionals:1.0.0")
|
||||
implementation("de.srsoftware:tools.util:2.0.3")
|
||||
implementation("org.bitbucket.b_c:jose4j:0.9.6")
|
||||
implementation("org.xerial:sqlite-jdbc:3.49.0.0")
|
||||
}
|
||||
@@ -2,7 +2,6 @@ description = "Umbrella : Web"
|
||||
|
||||
dependencies{
|
||||
implementation(project(":core"))
|
||||
implementation("de.srsoftware:tools.optionals:1.0.0")
|
||||
}
|
||||
|
||||
tasks.processResources {
|
||||
|
||||
Reference in New Issue
Block a user