diff --git a/backend/src/main/java/de/srsoftware/umbrella/backend/Application.java b/backend/src/main/java/de/srsoftware/umbrella/backend/Application.java index 91979d4..c231c6b 100644 --- a/backend/src/main/java/de/srsoftware/umbrella/backend/Application.java +++ b/backend/src/main/java/de/srsoftware/umbrella/backend/Application.java @@ -15,7 +15,8 @@ import de.srsoftware.umbrella.core.Util; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.documents.DocumentApi; import de.srsoftware.umbrella.items.ItemApi; -import de.srsoftware.umbrella.legacy.LegacyApi; +import de.srsoftware.umbrella.legacy.NotesLegacy; +import de.srsoftware.umbrella.legacy.UserLegacy; import de.srsoftware.umbrella.markdown.MarkdownApi; import de.srsoftware.umbrella.message.MessageSystem; import de.srsoftware.umbrella.notes.NoteModule; @@ -69,7 +70,8 @@ public class Application { var companyModule = new CompanyModule(registry, config); var documentApi = new DocumentApi(registry, config); var itemApi = new ItemApi(registry, config); - var legacyApi = new LegacyApi(registry,config); + var userLegacy = new UserLegacy(registry,config); + var notesLegacy = new NotesLegacy(registry,config); var markdownApi = new MarkdownApi(registry); var notesModule = new NoteModule(registry,config); var projectModule = new ProjectModule(registry, config); @@ -89,7 +91,8 @@ public class Application { timeModule .bindPath("/api/times") .on(server); translationModule.bindPath("/api/translations").on(server); userModule .bindPath("/api/user") .on(server); - legacyApi .bindPath("/legacy") .on(server); + notesLegacy .bindPath("/legacy/notes") .on(server); + userLegacy .bindPath("/legacy/user") .on(server); webHandler .bindPath("/") .on(server); server.setExecutor(Executors.newFixedThreadPool(threads)); diff --git a/core/src/main/java/de/srsoftware/umbrella/core/Constants.java b/core/src/main/java/de/srsoftware/umbrella/core/Constants.java index 75a032a..11478a2 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/Constants.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/Constants.java @@ -167,6 +167,7 @@ public class Constants { public static final String UMBRELLA = "Umbrella"; public static final String UNIT = "unit"; public static final String UNIT_PRICE = "unit_price"; + public static final String URI = "uri"; public static final String URL = "url"; public static final String USER = "user"; public static final String USER_ID = "user_id"; diff --git a/core/src/main/java/de/srsoftware/umbrella/core/api/NoteService.java b/core/src/main/java/de/srsoftware/umbrella/core/api/NoteService.java index 7dcfe9f..d0abd6d 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/api/NoteService.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/api/NoteService.java @@ -6,9 +6,9 @@ import de.srsoftware.umbrella.core.model.Note; import java.util.Map; public interface NoteService { - void deleteEntity(String task, long taskId); + void deleteEntity(String task, String taskId); - Map getNotes(String module, long entityId) throws UmbrellaException; + Map getNotes(String module, String entityId) throws UmbrellaException; Note save(Note note); } diff --git a/frontend/src/Components/Menu.svelte b/frontend/src/Components/Menu.svelte index 2787c38..ed11c49 100644 --- a/frontend/src/Components/Menu.svelte +++ b/frontend/src/Components/Menu.svelte @@ -9,7 +9,7 @@ const router = useTinyRouter(); const modules = $state([]); async function fetchModules(){ - const url = `${location.protocol}//${location.host.replace('5173','8080')}/legacy/modules`; + const url = `${location.protocol}//${location.host.replace('5173','8080')}/legacy/user/modules`; const resp = await fetch(url,{credentials:'include'}); if (resp.ok){ const arr = await resp.json(); diff --git a/frontend/src/routes/user/EditUser.svelte b/frontend/src/routes/user/EditUser.svelte index 0a6b06b..26abac4 100644 --- a/frontend/src/routes/user/EditUser.svelte +++ b/frontend/src/routes/user/EditUser.svelte @@ -7,7 +7,7 @@ import { t } from '../../translations.svelte.js'; let { user_id } = $props(); - let caption = $state(t('save_user')); + let caption = $state(t('save_object',{object:t('user')})); let editUser = $state(null); let message = $state(t('loading_data')); let options = $state([]); diff --git a/legacy/src/main/java/de/srsoftware/umbrella/legacy/NotesLegacy.java b/legacy/src/main/java/de/srsoftware/umbrella/legacy/NotesLegacy.java new file mode 100644 index 0000000..909f9f5 --- /dev/null +++ b/legacy/src/main/java/de/srsoftware/umbrella/legacy/NotesLegacy.java @@ -0,0 +1,57 @@ +/* © SRSoftware 2025 */ +package de.srsoftware.umbrella.legacy; + +import static de.srsoftware.umbrella.core.Constants.URI; +import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException; +import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.unprocessable; + +import com.sun.net.httpserver.HttpExchange; +import de.srsoftware.configuration.Configuration; +import de.srsoftware.tools.Path; +import de.srsoftware.umbrella.core.BaseHandler; +import de.srsoftware.umbrella.core.ModuleRegistry; +import java.io.IOException; + +public class NotesLegacy extends BaseHandler { + private final ModuleRegistry registry; + private final Configuration config; + + public NotesLegacy(ModuleRegistry registry, Configuration config) { + this.registry = registry; + this.config = config.subset("umbrella.modules").orElseThrow(() -> new RuntimeException("Missing configuration: umbrella.modules")); + } + + @Override + public boolean doDelete(Path path, HttpExchange ex) throws IOException { + return super.doDelete(path, ex); + } + + @Override + public boolean doGet(Path path, HttpExchange ex) throws IOException { + return super.doGet(path, ex); + } + + @Override + public boolean doOptions(Path path, HttpExchange ex) throws IOException { + return super.doOptions(path, ex); + } + + @Override + public boolean doPatch(Path path, HttpExchange ex) throws IOException { + return super.doPatch(path, ex); + } + + @Override + public boolean doPost(Path path, HttpExchange ex) throws IOException { + addCors(ex); + var data = formData(ex); + var noteService = registry.noteService(); + if (!(data.get(URI) instanceof String uri)) throw missingFieldException(URI); + var parts = uri.split(":",2); + if (parts.length<2) throw unprocessable("Expected URI to contain colon (:)!"); + String module = parts[0]; + String entityId = parts[1]; + var notes = noteService.getNotes(module,entityId); + return super.doPost(path, ex); + } +} diff --git a/legacy/src/main/java/de/srsoftware/umbrella/legacy/LegacyApi.java b/legacy/src/main/java/de/srsoftware/umbrella/legacy/UserLegacy.java similarity index 98% rename from legacy/src/main/java/de/srsoftware/umbrella/legacy/LegacyApi.java rename to legacy/src/main/java/de/srsoftware/umbrella/legacy/UserLegacy.java index 6adf846..025727c 100644 --- a/legacy/src/main/java/de/srsoftware/umbrella/legacy/LegacyApi.java +++ b/legacy/src/main/java/de/srsoftware/umbrella/legacy/UserLegacy.java @@ -29,13 +29,13 @@ import java.time.Instant; import java.util.*; import org.json.JSONObject; -public class LegacyApi extends BaseHandler { +public class UserLegacy extends BaseHandler { private final Configuration config; private final String messageUrl; private final ModuleRegistry registry; - public LegacyApi(ModuleRegistry registry, Configuration config) { + public UserLegacy(ModuleRegistry registry, Configuration config) { this.registry = registry; this.config = config.subset("umbrella.modules").orElseThrow(() -> new RuntimeException("Missing configuration: umbrella.modules")); this.messageUrl = null; @@ -270,7 +270,7 @@ public class LegacyApi extends BaseHandler { var keys = config.keys(); var match = false; for (var key : keys){ - var baseUrl = config.get(key + ".baseUrl").map(LegacyApi::stripTrailingSlash).orElse(null); + var baseUrl = config.get(key + ".baseUrl").map(UserLegacy::stripTrailingSlash).orElse(null); if (domain.equals(baseUrl)){ match = true; break; diff --git a/notes/src/main/java/de/srsoftware/umbrella/notes/NoteModule.java b/notes/src/main/java/de/srsoftware/umbrella/notes/NoteModule.java index bdc7850..6321db3 100644 --- a/notes/src/main/java/de/srsoftware/umbrella/notes/NoteModule.java +++ b/notes/src/main/java/de/srsoftware/umbrella/notes/NoteModule.java @@ -35,7 +35,7 @@ public class NoteModule extends BaseHandler implements NoteService { } @Override - public void deleteEntity(String module, long entityId) { + public void deleteEntity(String module, String entityId) { notesDb.deleteEntity(module,entityId); } @@ -79,8 +79,7 @@ public class NoteModule extends BaseHandler implements NoteService { notes = notesDb.list(user.get().id()); } else { var head = path.pop(); - long entityId = Long.parseLong(head); - notes = getNotes(module, entityId); + notes = getNotes(module, head); } var authors = notes.values().stream().map(Note::authorId).distinct().map(registry.userService()::loadUser).collect(Collectors.toMap(UmbrellaUser::id,UmbrellaUser::toMap)); return sendContent(ex, Map.of("notes",mapValues(notes),"authors",authors)); @@ -137,7 +136,7 @@ public class NoteModule extends BaseHandler implements NoteService { } } - public Map getNotes(String module, long entityId) throws UmbrellaException{ + public Map getNotes(String module, String entityId) throws UmbrellaException{ return notesDb.list(module,entityId); } diff --git a/notes/src/main/java/de/srsoftware/umbrella/notes/NotesDb.java b/notes/src/main/java/de/srsoftware/umbrella/notes/NotesDb.java index bcbd7ee..0583d7e 100644 --- a/notes/src/main/java/de/srsoftware/umbrella/notes/NotesDb.java +++ b/notes/src/main/java/de/srsoftware/umbrella/notes/NotesDb.java @@ -7,7 +7,7 @@ import java.util.Map; public interface NotesDb { long delete(long noteId); - void deleteEntity(String module, long entityId); + void deleteEntity(String module, String entityId); /** * get all lists of a person @@ -21,7 +21,7 @@ public interface NotesDb { * @param entityId * @return */ - Map list(String module, long entityId); + Map list(String module, String entityId); Note load(long noteId); diff --git a/notes/src/main/java/de/srsoftware/umbrella/notes/SqliteDb.java b/notes/src/main/java/de/srsoftware/umbrella/notes/SqliteDb.java index 403fa23..6ac5780 100644 --- a/notes/src/main/java/de/srsoftware/umbrella/notes/SqliteDb.java +++ b/notes/src/main/java/de/srsoftware/umbrella/notes/SqliteDb.java @@ -11,6 +11,7 @@ import static java.text.MessageFormat.format; import static java.time.ZoneOffset.UTC; import de.srsoftware.tools.jdbc.Query; +import de.srsoftware.umbrella.core.BaseDb; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.model.Note; import java.sql.Connection; @@ -18,58 +19,100 @@ import java.sql.SQLException; import java.util.HashMap; import java.util.Map; -public class SqliteDb implements NotesDb { +public class SqliteDb extends BaseDb implements NotesDb { private static final int INITIAL_DB_VERSION = 1; private static final System.Logger LOG = System.getLogger("NotesDB"); - private final Connection db; public SqliteDb(Connection conn) { - this.db = conn; - init(); + super(conn); } - private int createTables() { - createNotesTables(); - return createSettingsTable(); + protected int createTables() { + int currentVersion = createSettingsTable(); + switch (currentVersion){ + case 0: + createNotesTable(); + case 1: + addModuleColumn(); + addEntityIdColumn(); + calcReferences(); + dropUriColumn(); + } + + return setCurrentVersion(2); } - private int createSettingsTable() { + private void addEntityIdColumn() { var createTable = """ -CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255) NOT NULL); +ALTER TABLE notes +ADD COLUMN entity_id VARCHAR(255); """; try { - var stmt = db.prepareStatement(format(createTable,TABLE_SETTINGS, KEY, VALUE)); + var stmt = db.prepareStatement(createTable); stmt.execute(); stmt.close(); } catch (SQLException e) { - LOG.log(ERROR,ERROR_FAILED_CREATE_TABLE,TABLE_SETTINGS,e); + LOG.log(ERROR,"Failed to add \"entity_id\" column to table {0}",TABLE_NOTES,e); throw new RuntimeException(e); } + } - Integer version = null; + private void addModuleColumn() { + var createTable = """ +ALTER TABLE notes +ADD COLUMN module VARCHAR(20); +"""; try { - var rs = 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(); - } + var stmt = db.prepareStatement(createTable); + stmt.execute(); + stmt.close(); + } catch (SQLException e) { + LOG.log(ERROR,"Failed to add \"module\" column to table {0}",TABLE_NOTES,e); + throw new RuntimeException(e); + } + } + + private void calcReferences(){ + var createTable = """ +UPDATE notes +SET module = SUBSTR(uri, 1, INSTR(uri, ':') - 1), + entity_id = SUBSTR(uri, INSTR(uri, ':') + 1); +"""; + try { + var stmt = db.prepareStatement(createTable); + stmt.execute(); + stmt.close(); + } catch (SQLException e) { + LOG.log(ERROR,"Failed fill \"module\" and \"entity_id\" columns",e); + throw new RuntimeException(e); + } + } - return version; + private void createNotesTable() { + var createTable = """ +CREATE TABLE IF NOT EXISTS notes ( + id INTEGER PRIMARY KEY, + user_id INT NOT NULL, + uri VARCHAR(255) NOT NULL, + note TEXT NOT NULL, + timestamp INT)"""; + try { + var stmt = db.prepareStatement(createTable); + stmt.execute(); + stmt.close(); } catch (SQLException e) { - LOG.log(ERROR,ERROR_READ_TABLE,DB_VERSION,TABLE_SETTINGS,e); + LOG.log(ERROR,ERROR_FAILED_CREATE_TABLE,TABLE_NOTES,e); throw new RuntimeException(e); } } - private void createNotesTables() { + private void createNewNotesTable() { var createTable = """ CREATE TABLE IF NOT EXISTS "{0}" ( {1} INTEGER NOT NULL PRIMARY KEY, {2} TEXT NOT NULL, {3} VARCHAR(20) NOT NULL, - {4} INTEGER NOT NULL, + {4} VARCHAR(255) NOT NULL, {5} INTEGER NOT NULL, {6} DATETIME NOT NULL )"""; @@ -96,7 +139,7 @@ CREATE TABLE IF NOT EXISTS "{0}" ( } @Override - public void deleteEntity(String module, long entityId) { + public void deleteEntity(String module, String entityId) { try { Query.delete().from(TABLE_NOTES) .where(MODULE,equal(module)).where(ENTITY_ID,equal(entityId)) @@ -106,6 +149,18 @@ CREATE TABLE IF NOT EXISTS "{0}" ( } } + private void dropUriColumn(){ + var createTable = "ALTER TABLE notes DROP COLUMN uri"; + try { + var stmt = db.prepareStatement(createTable); + stmt.execute(); + stmt.close(); + } catch (SQLException e) { + LOG.log(ERROR,"Failed drop \"uri\" column",e); + throw new RuntimeException(e); + } + } + private void init(){ var version = createTables(); LOG.log(INFO,"Updated task db to version {0}",version); @@ -128,7 +183,7 @@ CREATE TABLE IF NOT EXISTS "{0}" ( } @Override - public Map list(String module, long entityId) { + public Map list(String module, String entityId) { try { var notes = new HashMap(); var rs = select(ALL).from(TABLE_NOTES).where(MODULE,equal(module)).where(ENTITY_ID,equal(entityId)).exec(db); diff --git a/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java b/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java index 9aa359a..c203470 100644 --- a/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java +++ b/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java @@ -55,7 +55,7 @@ public class TaskModule extends BaseHandler implements TaskService { var member = task.members().get(user.id()); if (member == null || !member.mayWrite()) throw forbidden("You are not allowed to delete {0}",task.name()); taskDb.delete(task); - registry.noteService().deleteEntity(TASK,taskId); + registry.noteService().deleteEntity(TASK,""+taskId); registry.tagService().deleteEntity(TASK,taskId); return sendContent(ex,Map.of(DELETED,taskId)); }