diff --git a/frontend/src/routes/search/Search.svelte b/frontend/src/routes/search/Search.svelte index 99aadba..a5f3d31 100644 --- a/frontend/src/routes/search/Search.svelte +++ b/frontend/src/routes/search/Search.svelte @@ -13,6 +13,7 @@ let fulltext = false; let key = $state(router.getQueryParam('key')); let input = $state(router.getQueryParam('key')); + let projects = $state(null); let tasks = $state(null); async function setKey(ev){ @@ -38,6 +39,7 @@ body: JSON.stringify(data) }; fetch(api('bookmark/search'),options).then(handleBookmarks); + fetch(api('project/search'),options).then(handleProjects); fetch(api('task/search'),options).then(handleTasks); } @@ -55,6 +57,15 @@ } } + async function handleProjects(resp){ + if (resp.ok){ + const res = await resp.json(); + projects = Object.keys(res).length ? res : null; + } else { + error = await resp.text(); + } + } + async function handleTasks(resp){ if (resp.ok){ const res = await resp.json(); @@ -85,6 +96,20 @@ +{#if projects} +
+ + {t('projects')} + + +
+{/if} {#if tasks}
diff --git a/project/src/main/java/de/srsoftware/umbrella/project/ProjectDb.java b/project/src/main/java/de/srsoftware/umbrella/project/ProjectDb.java index ad5f2b9..ac84d8f 100644 --- a/project/src/main/java/de/srsoftware/umbrella/project/ProjectDb.java +++ b/project/src/main/java/de/srsoftware/umbrella/project/ProjectDb.java @@ -5,13 +5,18 @@ 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.Collection; +import java.util.List; import java.util.Map; public interface ProjectDb { void dropMember(long projectId, long userId); + Map find(long userId, Collection keys, boolean fulltext); Map getMembers(Project project); Project load(long projectId) throws UmbrellaException; Map ofCompany(long companyId, boolean includeClosed) throws UmbrellaException; + Map ofUser(long userId, boolean includeClosed) throws UmbrellaException; Project save(Project prj) throws UmbrellaException; diff --git a/project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java b/project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java index 2a7ecda..a778087 100644 --- a/project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java +++ b/project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java @@ -4,6 +4,7 @@ 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.Paths.SEARCH; import static de.srsoftware.umbrella.core.Util.mapValues; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*; import static de.srsoftware.umbrella.core.model.Permission.*; @@ -29,12 +30,12 @@ import org.json.JSONObject; public class ProjectModule extends BaseHandler implements ProjectService { - private final ProjectDb projects; + private final ProjectDb projectDb; public ProjectModule(ModuleRegistry registry, Configuration config) throws UmbrellaException { super(registry); var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingFieldException(CONFIG_DATABASE)); - projects = new SqliteDb(connect(dbFile)); + projectDb = new SqliteDb(connect(dbFile)); } private void addMember(Project project, long userId) { @@ -101,6 +102,7 @@ public class ProjectModule extends BaseHandler implements ProjectService { var head = path.pop(); return switch (head) { case LIST -> postProjectList(ex, user.get()); + case SEARCH -> postSearch(user.get(),ex); case null -> postProject(ex, user.get()); default -> { var projectId = Long.parseLong(head); @@ -120,12 +122,12 @@ public class ProjectModule extends BaseHandler implements ProjectService { 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); + projectDb.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)); + var project = loadMembers(projectDb.load(projectId)); if (!project.hasMember(user)) throw forbidden("You are not a member of {0}",project.name()); var map = project.toMap(); project.companyId().map(companyService()::get).map(Company::toMap).ifPresent(data -> map.put(COMPANY,data)); @@ -133,7 +135,7 @@ public class ProjectModule extends BaseHandler implements ProjectService { } public Map listCompanyProjects(long companyId, boolean includeClosed) throws UmbrellaException { - var projectList = projects.ofCompany(companyId, includeClosed); + var projectList = projectDb.ofCompany(companyId, includeClosed); loadMembers(projectList.values()); return projectList; } @@ -147,7 +149,7 @@ public class ProjectModule extends BaseHandler implements ProjectService { @Override public Map listUserProjects(long userId, boolean includeClosed) throws UmbrellaException { - var projectMap = projects.ofUser(userId, includeClosed); + var projectMap = projectDb.ofUser(userId, includeClosed); loadMembers(projectMap.values()); return projectMap; } @@ -159,14 +161,14 @@ public class ProjectModule extends BaseHandler implements ProjectService { @Override public Project load(long projectId) { - return projects.load(projectId); + return projectDb.load(projectId); } @Override public Collection loadMembers(Collection projectList) { var userMap = new HashMap(); for (var project : projectList){ - for (var entry : projects.getMembers(project).entrySet()){ + for (var entry : projectDb.getMembers(project).entrySet()){ var userId = entry.getKey(); var permission = entry.getValue(); var user = userMap.computeIfAbsent(userId,k -> userService().loadUser(userId)); @@ -199,14 +201,14 @@ public class ProjectModule extends BaseHandler implements ProjectService { } private boolean patchProject(HttpExchange ex, long projectId, UmbrellaUser user) throws IOException, UmbrellaException { - var project = loadMembers(projects.load(projectId)); + var project = loadMembers(projectDb.load(projectId)); if (!project.hasMember(user)) throw forbidden("You are not a member of {0}",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); if (json.has(NEW_MEMBER) && json.get(NEW_MEMBER) instanceof Number num) addMember(project,num.longValue()); - projects.save(project.patch(json)); + projectDb.save(project.patch(json)); return sendContent(ex,project.toMap()); } @@ -218,7 +220,7 @@ public class ProjectModule extends BaseHandler implements ProjectService { 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)); + return sendContent(ex, projectDb.save(projectId,newState)); } private boolean postProject(HttpExchange ex, UmbrellaUser user) throws IOException, UmbrellaException { @@ -241,7 +243,7 @@ public class ProjectModule extends BaseHandler implements ProjectService { } var owner = Map.of(user.id(),new Member(user,OWNER)); var prj = new Project(0,name,description, OPEN.code(),companyId,showClosed, owner, PREDEFINED); - prj = projects.save(prj); + prj = projectDb.save(prj); if (json.has(TAGS) && json.get(TAGS) instanceof JSONArray arr){ var tagList = arr.toList().stream().filter(elem -> elem instanceof String).map(String.class::cast).toList(); @@ -257,4 +259,14 @@ public class ProjectModule extends BaseHandler implements ProjectService { if (json.has(COMPANY_ID) && json.get(COMPANY_ID) instanceof Number companyId) return listCompanyProjects(ex, user, companyId.longValue()); return listUserProjects(ex,user,showClosed); } + + + 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); + 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); + return sendContent(ex,mapValues(projects)); + } } \ No newline at end of file diff --git a/project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java b/project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java index f32a059..d6eaf07 100644 --- a/project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java +++ b/project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java @@ -9,6 +9,7 @@ import static de.srsoftware.umbrella.core.model.Status.COMPLETE; import static de.srsoftware.umbrella.core.model.Status.OPEN; import static de.srsoftware.umbrella.project.Constants.*; import static java.lang.System.Logger.Level.ERROR; +import static java.lang.System.Logger.Level.WARNING; import static java.text.MessageFormat.format; import de.srsoftware.umbrella.core.BaseDb; @@ -18,6 +19,7 @@ import de.srsoftware.umbrella.core.model.Project; import de.srsoftware.umbrella.core.model.Status; import java.sql.Connection; import java.sql.SQLException; +import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -163,7 +165,24 @@ CREATE TABLE IF NOT EXISTS {0} ( } } - + @Override + public Map find(long userId, Collection keys, boolean fulltext) { + try { + var projects = new HashMap(); + var query = select(ALL).from(TABLE_PROJECTS).leftJoin(ID,TABLE_PROJECT_USERS,PROJECT_ID).where(USER_ID, equal(userId)); + for (var key : keys) query.where(NAME,like("%"+key+"%")); + LOG.log(WARNING,"Full-text search not implemented for projects"); + 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 new UmbrellaException("Failed to load items from database"); + } + } @Override public Map ofUser(long userId, boolean includeClosed) throws UmbrellaException { diff --git a/task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java b/task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java index f6404ad..aac2731 100644 --- a/task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java +++ b/task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java @@ -121,9 +121,7 @@ CREATE TABLE IF NOT EXISTS {0} ( var query = select(ALL).from(TABLE_TASKS).leftJoin(ID,TABLE_TASKS_USERS,TASK_ID) .where(USER_ID,equal(userId)); for (var key : keys) query.where(NAME,like("%"+key+"%")); - if (fulltext) { - for (var key : keys) query.where(DESCRIPTION,like("%"+key+"%")); - } + LOG.log(WARNING,"Full-text search not implemented for tasks"); var rs = query.exec(db); while (rs.next()){