Merge branch 'feature/translation' into dev
All checks were successful
Build Docker Image / Docker-Build (push) Successful in 2m41s
Build Docker Image / Clean-Registry (push) Successful in 2s

This commit is contained in:
2026-01-15 14:31:32 +01:00
105 changed files with 1480 additions and 1205 deletions

View File

@@ -2,16 +2,20 @@
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.ModuleRegistry.*;
import static de.srsoftware.umbrella.core.Paths.LIST;
import static de.srsoftware.umbrella.core.Paths.SEARCH;
import static de.srsoftware.umbrella.core.Util.mapValues;
import static de.srsoftware.umbrella.core.constants.Field.*;
import static de.srsoftware.umbrella.core.constants.Field.SETTINGS;
import static de.srsoftware.umbrella.core.constants.Field.TAGS;
import static de.srsoftware.umbrella.core.constants.Module.PROJECT;
import static de.srsoftware.umbrella.core.constants.Path.*;
import static de.srsoftware.umbrella.core.constants.Text.*;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
import static de.srsoftware.umbrella.core.model.Permission.*;
import static de.srsoftware.umbrella.core.model.Permission.OWNER;
import static de.srsoftware.umbrella.core.model.Status.OPEN;
import static de.srsoftware.umbrella.core.model.Status.PREDEFINED;
import static de.srsoftware.umbrella.core.model.Translatable.t;
import static de.srsoftware.umbrella.messagebus.MessageBus.messageBus;
import static de.srsoftware.umbrella.messagebus.events.Event.EventType.CREATE;
import static de.srsoftware.umbrella.project.Constants.CONFIG_DATABASE;
@@ -20,11 +24,12 @@ import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
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.ModuleRegistry;
import de.srsoftware.umbrella.core.*;
import de.srsoftware.umbrella.core.api.ProjectService;
import de.srsoftware.umbrella.core.constants.Field;
import de.srsoftware.umbrella.core.constants.Path;
import de.srsoftware.umbrella.core.constants.Text;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.core.model.*;
import de.srsoftware.umbrella.messagebus.events.ProjectEvent;
@@ -39,7 +44,7 @@ public class ProjectModule extends BaseHandler implements ProjectService {
public ProjectModule(Configuration config) throws UmbrellaException {
super();
var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingFieldException(CONFIG_DATABASE));
var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingField(CONFIG_DATABASE));
projectDb = new SqliteDb(connect(dbFile));
ModuleRegistry.add(this);
}
@@ -52,7 +57,7 @@ public class ProjectModule extends BaseHandler implements ProjectService {
}
@Override
public boolean doGet(Path path, HttpExchange ex) throws IOException {
public boolean doGet(de.srsoftware.tools.Path path, HttpExchange ex) throws IOException {
addCors(ex);
try {
Optional<Token> token = SessionToken.from(ex).map(Token::of);
@@ -76,7 +81,7 @@ public class ProjectModule extends BaseHandler implements ProjectService {
}
@Override
public boolean doPatch(Path path, HttpExchange ex) throws IOException {
public boolean doPatch(de.srsoftware.tools.Path path, HttpExchange ex) throws IOException {
addCors(ex);
try {
Optional<Token> token = SessionToken.from(ex).map(Token::of);
@@ -99,7 +104,7 @@ public class ProjectModule extends BaseHandler implements ProjectService {
}
@Override
public boolean doPost(Path path, HttpExchange ex) throws IOException {
public boolean doPost(de.srsoftware.tools.Path path, HttpExchange ex) throws IOException {
addCors(ex);
try {
Optional<Token> token = SessionToken.from(ex).map(Token::of);
@@ -114,7 +119,7 @@ public class ProjectModule extends BaseHandler implements ProjectService {
var projectId = Long.parseLong(head);
head = path.pop();
yield switch (head){
case STATE -> postNewState(ex,projectId,user.get());
case Path.STATE -> postNewState(ex,projectId,user.get());
case null, default -> super.doGet(path, ex);
};
}
@@ -134,9 +139,9 @@ public class ProjectModule extends BaseHandler implements ProjectService {
private boolean getProject(HttpExchange ex, long projectId, UmbrellaUser user) throws IOException, UmbrellaException {
var project = loadMembers(projectDb.load(projectId));
if (!project.hasMember(user)) throw forbidden("You are not a member of {0}",project.name());
if (!project.hasMember(user)) throw notAmember(t(PROJECT_WITH_ID,ID,project.name()));
var map = project.toMap();
project.companyId().map(companyService()::get).map(Company::toMap).ifPresent(data -> map.put(COMPANY,data));
project.companyId().map(companyService()::get).map(Company::toMap).ifPresent(data -> map.put(Field.COMPANY,data));
return sendContent(ex,map);
}
@@ -148,7 +153,7 @@ public class ProjectModule extends BaseHandler implements ProjectService {
private boolean listCompanyProjects(HttpExchange ex, UmbrellaUser user, long companyId) throws IOException, UmbrellaException {
var company = companyService().get(companyId);
if (!companyService().membership(companyId,user.id())) throw forbidden("You are mot a member of company {0}",company.name());
if (!companyService().membership(companyId,user.id())) throw notAmember(t(COMPANY_WITH_ID,ID,company.name()));
var projects = listCompanyProjects(companyId,false);
return sendContent(ex,mapValues(projects));
}
@@ -192,9 +197,9 @@ public class ProjectModule extends BaseHandler implements ProjectService {
try {
userId = Long.parseLong(key);
} catch (NumberFormatException e) {
throw invalidFieldException(USER_ID,"long");
throw invalidField(USER_ID,t(LONG));
}
if (!(json.get(key) instanceof Number number)) throw invalidFieldException(PERMISSION,"int");
if (!(json.get(key) instanceof Number number)) throw invalidField(PERMISSION,t(Text.NUMBER));
var permission = Permission.of(number.intValue());
if (permission == OWNER) { // if a new person is about to become the project owner
for (var member : members.values()){ // alter the previous owners to editors
@@ -208,8 +213,8 @@ public class ProjectModule extends BaseHandler implements ProjectService {
private boolean patchProject(HttpExchange ex, long projectId, UmbrellaUser user) throws IOException, UmbrellaException {
var project = loadMembers(projectDb.load(projectId));
if (!project.hasMember(user)) throw forbidden("You are not a member of {0}",project.name());
var old = project.toMap();
if (!project.hasMember(user)) throw notAmember(t(PROJECT_WITH_ID,ID,project.name()));
var json = json(ex);
if (json.has(DROP_MEMBER) && json.get(DROP_MEMBER) instanceof Number id) dropMember(project,id.longValue());
if (json.has(MEMBERS) && json.get(MEMBERS) instanceof JSONObject memberJson) patchMembers(project,memberJson);
@@ -223,17 +228,17 @@ public class ProjectModule extends BaseHandler implements ProjectService {
private boolean postNewState(HttpExchange ex, long projectId, UmbrellaUser user) throws IOException {
var project = loadMembers(load(projectId));
if (!project.hasMember(user)) throw forbidden("You are not a member of {0}",project.name());
if (!project.hasMember(user)) throw notAmember(t(PROJECT_WITH_ID,ID,project.name()));
var json = json(ex);
if (!(json.has(CODE) && json.get(CODE) instanceof Number code)) throw missingFieldException(CODE);
if (!(json.has(NAME) && json.get(NAME) instanceof String name)) throw missingFieldException(NAME);
if (!(json.has(Field.CODE) && json.get(Field.CODE) instanceof Number code)) throw missingField(Field.CODE);
if (!(json.has(NAME) && json.get(NAME) instanceof String name)) throw missingField(NAME);
var newState = new Status(name,code.intValue());
return sendContent(ex, projectDb.save(projectId,newState));
}
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);
if (!(json.has(NAME) && json.get(NAME) instanceof String name)) throw missingField(NAME);
String description = null;
if (json.has(DESCRIPTION)){
var desc = json.get(DESCRIPTION);
@@ -242,7 +247,7 @@ public class ProjectModule extends BaseHandler implements ProjectService {
}
Long companyId = null;
if (json.has(COMPANY_ID) && json.get(COMPANY_ID) instanceof Number number){
if (!companyService().membership(number.longValue(), user.id())) throw forbidden("You are not a member of company {0}!",number);
if (!companyService().membership(number.longValue(), user.id())) throw notAmember(t(COMPANY_WITH_ID, ID,number));
companyId = number.longValue();
}
var showClosed = false;
@@ -271,7 +276,7 @@ public class ProjectModule extends BaseHandler implements ProjectService {
private boolean postSearch(UmbrellaUser user, HttpExchange ex) throws IOException {
var json = json(ex);
if (!(json.has(KEY) && json.get(KEY) instanceof String key)) throw missingFieldException(KEY);
if (!(json.has(KEY) && json.get(KEY) instanceof String key)) throw missingField(KEY);
var keys = Arrays.asList(key.split(" "));
var fulltext = json.has(FULLTEXT) && json.get(FULLTEXT) instanceof Boolean val && val;
var projects = projectDb.find(user.id(),keys,fulltext);

View File

@@ -4,22 +4,22 @@ package de.srsoftware.umbrella.project;
import static de.srsoftware.tools.jdbc.Condition.*;
import static de.srsoftware.tools.jdbc.Query.*;
import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL;
import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.Errors.*;
import static de.srsoftware.umbrella.core.ModuleRegistry.translator;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.databaseException;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.notFound;
import static de.srsoftware.umbrella.core.constants.Constants.TABLE_SETTINGS;
import static de.srsoftware.umbrella.core.constants.Field.*;
import static de.srsoftware.umbrella.core.constants.Field.TYPE;
import static de.srsoftware.umbrella.core.constants.Text.*;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
import static de.srsoftware.umbrella.core.model.Status.COMPLETE;
import static de.srsoftware.umbrella.core.model.Status.OPEN;
import static de.srsoftware.umbrella.core.model.Translatable.t;
import static de.srsoftware.umbrella.project.Constants.*;
import static java.text.MessageFormat.format;
import de.srsoftware.umbrella.core.BaseDb;
import de.srsoftware.umbrella.core.constants.Field;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.core.model.Permission;
import de.srsoftware.umbrella.core.model.Project;
import de.srsoftware.umbrella.core.model.Status;
import de.srsoftware.umbrella.core.model.UmbrellaUser;
import de.srsoftware.umbrella.core.model.*;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
@@ -48,7 +48,7 @@ public class SqliteDb extends BaseDb implements ProjectDb {
stmt.execute();
stmt.close();
} catch (SQLException e) {
throw databaseException(FAILED_TO_CREATE_TABLE,TABLE_PROJECTS).causedBy(e);
throw failedToCreateTable(TABLE_PROJECTS).causedBy(e);
}
}
@@ -66,7 +66,7 @@ PRIMARY KEY (project_id, code)
stmt.execute();
stmt.close();
} catch (SQLException e) {
throw databaseException(FAILED_TO_CREATE_TABLE,TABLE_CUSTOM_STATES).causedBy(e);
throw failedToCreateTable(TABLE_CUSTOM_STATES).causedBy(e);
}
}
@@ -98,7 +98,7 @@ CREATE TABLE IF NOT EXISTS {0} (
stmt.execute();
stmt.close();
} catch (SQLException e) {
throw databaseException(FAILED_TO_CREATE_TABLE,TABLE_PROJECT_USERS).causedBy(e);
throw failedToCreateTable(TABLE_PROJECT_USERS).causedBy(e);
}
}
@@ -111,7 +111,7 @@ CREATE TABLE IF NOT EXISTS {0} (
.where(USER_ID,equal(userId))
.execute(db);
} catch (SQLException e) {
throw databaseException(FAILED_TO_DROP_ENTITY_OF_ENTITY,"member",userId,"project",projectId).causedBy(e);
throw failedToDropObjectFromObject("member",userId,t(PROJECT),projectId).causedBy(e);
}
}
@@ -124,7 +124,7 @@ CREATE TABLE IF NOT EXISTS {0} (
rs.close();
return result;
} catch (SQLException e){
throw databaseException(FAILED_TO_LOAD_ENTITY_MEMBERS,PROJECT,project.name()).causedBy(e);
throw failedToLoadMembers(t(PROJECT_WITH_ID,ID,project.name())).causedBy(e);
}
}
@@ -136,7 +136,7 @@ CREATE TABLE IF NOT EXISTS {0} (
if (rs.next()) project = Project.of(rs);
rs.close();
if (project == null) throw notFound("no_project_for_id",projectId);
if (project == null) throw notFound("No project for id {id}", ID,projectId);
rs = select(ALL).from(TABLE_CUSTOM_STATES).where(PROJECT_ID,equal(projectId)).exec(db);
var states = project.allowedStates();
@@ -151,7 +151,7 @@ CREATE TABLE IF NOT EXISTS {0} (
rs.close();
return project;
} catch (SQLException e) {
throw databaseException(FAILED_TO_LOAD_ENTITY,"project").causedBy(e);
throw failedToLoadObject(t(PROJECT),projectId).causedBy(e);
}
}
@@ -173,7 +173,7 @@ CREATE TABLE IF NOT EXISTS {0} (
rs.close();
return projects;
} catch (SQLException e) {
throw databaseException(FAILED_TO_LOAD_ENTITY,"items").causedBy(e);
throw failedToLoadObject("items").causedBy(e);
}
}
@@ -181,9 +181,9 @@ CREATE TABLE IF NOT EXISTS {0} (
public Map<Long, Project> find(long userId, Collection<String> keys, boolean fulltext) {
try {
var projects = new HashMap<Long,Project>();
var query = select(ALL).from(TABLE_PROJECTS).leftJoin(ID,TABLE_PROJECT_USERS,PROJECT_ID).where(USER_ID, equal(userId));
var query = select(ALL).from(TABLE_PROJECTS).leftJoin(ID,TABLE_PROJECT_USERS, PROJECT_ID).where(USER_ID, equal(userId));
if (fulltext) {
for (var key : keys) query.where(format("CONCAT({0},\" \",{1})",NAME,DESCRIPTION),like("%"+key+"%"));
for (var key : keys) query.where(format("CONCAT({0},\" \",{1})", NAME, DESCRIPTION),like("%"+key+"%"));
} else {
for (var key : keys) query.where(NAME,like("%"+key+"%"));
}
@@ -195,7 +195,7 @@ CREATE TABLE IF NOT EXISTS {0} (
rs.close();
return projects;
} catch (SQLException e) {
throw databaseException(FAILED_TO_LIST_ENTITIES,"items").causedBy(e);
throw databaseException(FAILED_TO_LIST_ENTITIES, TYPE,t(ITEMS)).causedBy(e);
}
}
@@ -203,7 +203,7 @@ CREATE TABLE IF NOT EXISTS {0} (
public Map<Long, Project> ofUser(long userId, boolean includeClosed) throws UmbrellaException {
try {
var projects = new HashMap<Long,Project>();
var query = select(ALL).from(TABLE_PROJECTS).leftJoin(ID,TABLE_PROJECT_USERS,PROJECT_ID).where(USER_ID, equal(userId));
var query = select(ALL).from(TABLE_PROJECTS).leftJoin(ID,TABLE_PROJECT_USERS, PROJECT_ID).where(USER_ID, equal(userId));
if (!includeClosed) query = query.where(STATUS,lessThan(COMPLETE.code()));
var rs = query.exec(db);
while (rs.next()){
@@ -213,7 +213,7 @@ CREATE TABLE IF NOT EXISTS {0} (
rs.close();
return projects;
} catch (SQLException e) {
throw databaseException(FAILED_TO_LIST_ENTITIES).causedBy(e);
throw databaseException(FAILED_TO_LIST_ENTITIES, t("projects")).causedBy(e);
}
}
@@ -234,29 +234,29 @@ CREATE TABLE IF NOT EXISTS {0} (
return new Project(id, prj.name(), prj.description(),prj.status(),prj.companyId().orElse(null),prj.showClosed(),prj.members(),prj.allowedStates());
}
} catch (SQLException e) {
throw databaseException(FAILED_TO_STORE_ENTITY,PROJECT).causedBy(e);
throw failedToStoreObject(prj).causedBy(e);
}
} else { // Update
try {
if (prj.isDirty(MEMBERS)){
var query = replaceInto(TABLE_PROJECT_USERS,PROJECT_ID,USER_ID,PERMISSIONS);
var query = replaceInto(TABLE_PROJECT_USERS, PROJECT_ID, USER_ID,PERMISSIONS);
for (var member : prj.members().entrySet()) query.values(prj.id(),member.getKey(),member.getValue().permission().code());
query.execute(db).close();
prj.clean(MEMBERS);
}
if (prj.isDirty(TAG_COLORS)){
replaceInto(TABLE_SETTINGS,KEY,VALUE).values(colorKey(prj.id()),new JSONObject(prj.tagColors()).toString()).execute(db).close();
replaceInto(TABLE_SETTINGS, KEY,VALUE).values(colorKey(prj.id()),new JSONObject(prj.tagColors()).toString()).execute(db).close();
prj.clean(TAG_COLORS);
}
if (prj.isDirty()){
update(TABLE_PROJECTS).set(NAME,DESCRIPTION,STATUS,COMPANY_ID,SHOW_CLOSED).where(ID,equal(prj.id())).prepare(db)
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(),prj.companyId(),prj.showClosed())
.execute();
prj.clean();
}
return prj;
} catch (SQLException e) {
throw databaseException(FAILED_TO_UPDATE_ENTITY, translator().translate(user.language(),"project")).causedBy(e);
throw databaseException(FAILED_TO_UPDATE_OBJECT, t(PROJECT_WITH_ID, ID,prj.name())).causedBy(e);
}
}
return null;
@@ -266,7 +266,7 @@ CREATE TABLE IF NOT EXISTS {0} (
@Override
public Status save(long projectId, Status newState) {
try {
insertInto(TABLE_CUSTOM_STATES,PROJECT_ID,CODE,NAME).values(projectId,newState.code(),newState.name()).execute(db).close();
insertInto(TABLE_CUSTOM_STATES, PROJECT_ID, Field.CODE, NAME).values(projectId,newState.code(),newState.name()).execute(db).close();
return newState;
} catch (SQLException e) {
throw databaseException(FAILED_TO_CREATE_STATE).causedBy(e);