completed user-defined states

This commit is contained in:
2025-08-01 21:53:48 +02:00
parent 0511faa342
commit 64e925c83f
8 changed files with 161 additions and 59 deletions

View File

@@ -3,9 +3,11 @@ package de.srsoftware.umbrella.project;
public class Constants {
private Constants(){}
public static final String CONFIG_DATABASE = "umbrella.modules.project.database";
public static final String DB_VERSION = "project_db_version";
public static final String PERMISSIONS = "permissions";
public static final String TABLE_CUSTOM_STATES = "custom_states";
public static final String TABLE_PROJECTS = "projects";
public static final String TABLE_PROJECT_USERS = "projects_users";

View File

@@ -4,6 +4,8 @@ package de.srsoftware.umbrella.project;
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 java.util.Map;
public interface ProjectDb {
@@ -14,4 +16,6 @@ public interface ProjectDb {
Map<Long, Project> ofUser(long userId, boolean includeClosed) throws UmbrellaException;
Project save(Project prj) throws UmbrellaException;
Status save(long projectId, Status newState);
}

View File

@@ -12,6 +12,7 @@ import static de.srsoftware.umbrella.core.model.Status.OPEN;
import static de.srsoftware.umbrella.core.model.Status.PREDEFINED;
import static de.srsoftware.umbrella.project.Constants.CONFIG_DATABASE;
import static java.lang.Boolean.TRUE;
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
import static java.net.HttpURLConnection.HTTP_OK;
import com.sun.net.httpserver.HttpExchange;
@@ -73,7 +74,6 @@ public class ProjectModule extends BaseHandler implements ProjectService {
head = path.pop();
yield switch (head) {
case null -> getProject(ex, projectId, user.get());
case STATES -> super.doGet(path, ex); // TODO THIS SHOULD NO LONGER BE REQUIRED
default -> super.doGet(path, ex);
};
}
@@ -121,15 +121,30 @@ public class ProjectModule extends BaseHandler implements ProjectService {
if (user.isEmpty()) return unauthorized(ex);
var head = path.pop();
return switch (head) {
case LIST -> postProjectList(ex,user.get());
case null -> postProject(ex,user.get());
default -> super.doGet(path,ex);
case LIST -> postProjectList(ex, user.get());
case null -> postProject(ex, user.get());
default -> {
var projectId = Long.parseLong(head);
head = path.pop();
yield switch (head){
case STATE -> postNewState(ex,projectId,user.get());
case null, default -> super.doGet(path, ex);
};
}
};
} catch (NumberFormatException e){
return sendContent(ex,HTTP_BAD_REQUEST,"Invalid project id");
} catch (UmbrellaException e){
return send(ex,e);
}
}
private void dropMember(Project project, long userId) {
if (project.members().get(userId).permission() == OWNER) throw forbidden("You may not remove the owner of the project");
projects.dropMember(project.id(),userId);
project.members().remove(userId);
}
private boolean getProject(HttpExchange ex, long projectId, UmbrellaUser user) throws IOException, UmbrellaException {
var project = loadMembers(projects.load(projectId));
if (!project.hasMember(user)) throw forbidden("You are not a member of {0}",project.name());
@@ -216,12 +231,16 @@ public class ProjectModule extends BaseHandler implements ProjectService {
return sendContent(ex,project.toMap());
}
private void dropMember(Project project, long userId) {
if (project.members().get(userId).permission() == OWNER) throw forbidden("You may not remove the owner of the project");
projects.dropMember(project.id(),userId);
project.members().remove(userId);
}
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());
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);
var newState = new Status(name,code.intValue());
return sendContent(ex,projects.save(projectId,newState));
}
private boolean postProject(HttpExchange ex, UmbrellaUser user) throws IOException, UmbrellaException {
var json = json(ex);

View File

@@ -17,7 +17,10 @@ import de.srsoftware.tools.jdbc.Query;
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 java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
@@ -34,40 +37,60 @@ public class SqliteDb implements ProjectDb {
init();
}
private int createTables() {
createProjectTables();
return createSettingsTable();
}
private void createProjectTables() {
private void createProjectTable() {
var createTable = """
CREATE TABLE IF NOT EXISTS {0} (
`{1}` INTEGER PRIMARY KEY,
`{2}` INTEGER,
`{3}` VARCHAR(255) NOT NULL,
`{4}` TEXT,
`{5}` INT DEFAULT {6},
`{7}` BOOLEAN DEFAULT 0
)""";
CREATE TABLE IF NOT EXISTS {0} (
`{1}` INTEGER PRIMARY KEY,
`{2}` INTEGER,
`{3}` VARCHAR(255) NOT NULL,
`{4}` TEXT,
`{5}` INT DEFAULT {6},
`{7}` BOOLEAN DEFAULT 0
)""";
try {
var stmt = db.prepareStatement(format(createTable,TABLE_PROJECTS, ID, COMPANY_ID, NAME, DESCRIPTION, STATUS, OPEN.code(), SHOW_CLOSED));
var stmt = db.prepareStatement(format(createTable, TABLE_PROJECTS, ID, COMPANY_ID, NAME, DESCRIPTION, STATUS, OPEN.code(), SHOW_CLOSED));
stmt.execute();
stmt.close();
} catch (SQLException e) {
LOG.log(ERROR,ERROR_FAILED_CREATE_TABLE,TABLE_PROJECTS ,e);
LOG.log(ERROR, ERROR_FAILED_CREATE_TABLE, TABLE_PROJECTS, e);
throw new RuntimeException(e);
}
}
private void createStatesTable(){
var sql = """
CREATE TABLE IF NOT EXISTS custom_states (
project_id LONG NOT NULL,
code INT NOT NULL,
name VARCHAR(64) NOT NULL,
PRIMARY KEY (project_id, code)
);
""";
try {
var stmt = db.prepareStatement(format(sql));
stmt.execute();
stmt.close();
} catch (SQLException e) {
LOG.log(ERROR, ERROR_FAILED_CREATE_TABLE, TABLE_CUSTOM_STATES, e);
throw new RuntimeException(e);
}
long count = 0L;
try {
ResultSet rs = select("COUNT(*)").from(TABLE_PROJECTS).exec(db);
if (rs.next()) count = rs.getLong(1);
rs.close();
} catch (SQLException ignored) {
// go on with table creation
}
}
createTable = """
private int createTables() {
int currentVersion = createSettingsTable();
switch (currentVersion){
case 0:
createProjectTable();
createUsersTable();
case 1:
createStatesTable();
}
return setCurrentVersion(2);
}
private void createUsersTable(){
var createTable = """
CREATE TABLE IF NOT EXISTS {0} (
{1} INT NOT NULL,
{2} INT NOT NULL,
@@ -97,15 +120,11 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255)
throw new RuntimeException(e);
}
Integer version = null;
var version = 0;
try {
var rs = Query.select(VALUE).from(TABLE_SETTINGS).where(KEY, equal(DB_VERSION)).exec(db);
if (rs.next()) version = rs.getInt(VALUE);
rs.close();
if (version == null) {
version = INITIAL_DB_VERSION;
insertInto(TABLE_SETTINGS, KEY, VALUE).values(DB_VERSION,version).execute(db).close();
}
return version;
} catch (SQLException e) {
@@ -152,6 +171,10 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255)
if (rs.next()) result = Project.of(rs);
rs.close();
if (result == null) throw UmbrellaException.notFound("No project found for id {0}",projectId);
rs = select(ALL).from(TABLE_CUSTOM_STATES).where(PROJECT_ID,equal(projectId)).exec(db);
var states = result.allowedStates();
while (rs.next()) states.add(Status.of(rs));
rs.close();
return result;
} catch (SQLException e) {
throw new UmbrellaException("Failed to load project from database");
@@ -236,4 +259,24 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255)
}
return null;
}
@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();
return newState;
} catch (SQLException e) {
throw new UmbrellaException("Failed to create custom state!");
}
}
private int setCurrentVersion(int version) {
try {
replaceInto(TABLE_SETTINGS, KEY, VALUE).values(DB_VERSION,version).execute(db).close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
return version;
}
}