Files
Umbrella/project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java
T
StephanRichter 9bec33d5de
Build Docker Image / Docker-Build (push) Successful in 3m32s
Build Docker Image / Clean-Registry (push) Successful in 1s
implemented editing of custom states
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
2026-05-11 20:14:24 +02:00

277 lines
9.1 KiB
Java

/* © SRSoftware 2025 */
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.Errors.*;
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.PROJECT;
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.*;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.json.JSONObject;
public class SqliteDb extends BaseDb implements ProjectDb {
public SqliteDb(Connection connection) {
super(connection);
}
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
)""";
try {
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) {
throw failedToCreateTable(TABLE_PROJECTS).causedBy(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) {
throw failedToCreateTable(TABLE_CUSTOM_STATES).causedBy(e);
}
}
protected int createTables() {
int currentVersion = createSettingsTable();
switch (currentVersion){
case 0:
createProjectTable();
createUsersTable();
case 1:
createStatesTable();
case 2:
swapStates(TABLE_PROJECTS);
}
return setCurrentVersion(3);
}
private void createUsersTable(){
var createTable = """
CREATE TABLE IF NOT EXISTS {0} (
{1} INT NOT NULL,
{2} INT NOT NULL,
{3} INT DEFAULT {3},
PRIMARY KEY ({1}, {2})
)""";
try {
var stmt = db.prepareStatement(format(createTable,TABLE_PROJECT_USERS, PROJECT_ID, USER_ID, PERMISSIONS));
stmt.execute();
stmt.close();
} catch (SQLException e) {
throw failedToCreateTable(TABLE_PROJECT_USERS).causedBy(e);
}
}
@Override
public void dropMember(long projectId, long userId) {
try {
delete().from(TABLE_PROJECT_USERS)
.where(PROJECT_ID,equal(projectId))
.where(USER_ID,equal(userId))
.execute(db);
} catch (SQLException e) {
throw failedToDropObjectFromObject("member",userId,t(PROJECT),projectId).causedBy(e);
}
}
@Override
public Map<Long, Permission> getMembers(Project project) {
try {
var result = new HashMap<Long,Permission>();
var rs = select(ALL).from(TABLE_PROJECT_USERS).where(PROJECT_ID,equal(project.id())).exec(db);
while (rs.next()) result.put(rs.getLong(USER_ID),Permission.of(rs.getInt(PERMISSIONS)));
rs.close();
return result;
} catch (SQLException e){
throw failedToLoadMembers(t(PROJECT_WITH_ID,ID,project.name())).causedBy(e);
}
}
@Override
public Project load(long projectId) throws UmbrellaException {
try {
var rs = select(ALL).from(TABLE_PROJECTS).where(ID, equal(projectId)).exec(db);
Project project = null;
if (rs.next()) project = Project.of(rs);
rs.close();
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();
while (rs.next()) states.add(Status.of(rs));
rs.close();
rs = select(VALUE).from(TABLE_SETTINGS).where(KEY,equal(colorKey(projectId))).exec(db);
if (rs.next()) {
var map = project.tagColors();
new JSONObject(rs.getString(VALUE)).toMap().forEach((k, v) -> map.put(k.toLowerCase(), v.toString()));
}
rs.close();
return project;
} catch (SQLException e) {
throw failedToLoadObject(t(PROJECT),projectId).causedBy(e);
}
}
private String colorKey(long projectId) {
return "tag_colors:"+projectId;
}
@Override
public Map<Long, Project> ofCompany(long companyId, boolean includeClosed) throws UmbrellaException {
try {
var projects = new HashMap<Long,Project>();
var query = select(ALL).from(TABLE_PROJECTS).where(COMPANY_ID, equal(companyId));
if (!includeClosed) query = query.where(STATUS,lessThan(COMPLETE.code()));
var rs = query.exec(db);
while (rs.next()){
var project = Project.of(rs);
projects.put(project.id(),project);
}
rs.close();
return projects;
} catch (SQLException e) {
throw failedToLoadObject("items").causedBy(e);
}
}
@Override
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));
if (fulltext) {
for (var key : keys) query.where(format("CONCAT({0},\" \",{1})", NAME, DESCRIPTION),like("%"+key+"%"));
} else {
for (var key : keys) query.where(NAME,like("%"+key+"%"));
}
var rs = query.exec(db);
while (rs.next()){
var project = Project.of(rs);
projects.put(project.id(),project);
}
rs.close();
return projects;
} catch (SQLException e) {
throw databaseException(FAILED_TO_LIST_ENTITIES, TYPE,t(ITEMS)).causedBy(e);
}
}
@Override
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));
if (!includeClosed) query = query.where(STATUS,lessThan(COMPLETE.code()));
var rs = query.exec(db);
while (rs.next()){
var project = Project.of(rs);
projects.put(project.id(),project);
}
rs.close();
return projects;
} catch (SQLException e) {
throw databaseException(FAILED_TO_LIST_ENTITIES, t("projects")).causedBy(e);
}
}
@Override
public Project save(Project prj, UmbrellaUser user) throws UmbrellaException {
if (prj.id() == 0) { // new
try {
var stmt = insertInto(TABLE_PROJECTS, NAME, DESCRIPTION, STATUS, COMPANY_ID, SHOW_CLOSED).values(prj.name(), prj.description(), prj.status(), prj.companyId().orElse(null), prj.showClosed()).execute(db);
var rs = stmt.getGeneratedKeys();
var id = rs.next() ? rs.getLong(1) : null;
rs.close();
if (id != null){
if (!prj.members().isEmpty()) {
var query = insertInto(TABLE_PROJECT_USERS, PROJECT_ID, USER_ID, PERMISSIONS);
for (var member : prj.members().entrySet()) query.values(id, member.getKey(), member.getValue().permission().code());
query.execute(db).close();
}
return new Project(id, prj.name(), prj.description(),prj.status(),prj.companyId().orElse(null),prj.showClosed(),prj.members(),prj.allowedStates());
}
} catch (SQLException e) {
throw failedToStoreObject(prj).causedBy(e);
}
} else { // Update
try {
if (prj.isDirty(MEMBERS)){
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();
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)
.apply(prj.name(),prj.description(),prj.status(),prj.companyId().orElse(null),prj.showClosed())
.execute();
prj.clean();
}
return prj;
} catch (SQLException e) {
throw databaseException(FAILED_TO_UPDATE_OBJECT, t(PROJECT_WITH_ID, ID,prj.name())).causedBy(e);
}
}
return null;
}
@Override
public Status save(long projectId, Status newState) {
try {
replaceInto(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);
}
}
}