extended search to projects

This commit is contained in:
2025-08-19 09:53:17 +02:00
parent dbc091cc3c
commit 990e8253e2
5 changed files with 75 additions and 16 deletions

View File

@@ -13,6 +13,7 @@
let fulltext = false; let fulltext = false;
let key = $state(router.getQueryParam('key')); let key = $state(router.getQueryParam('key'));
let input = $state(router.getQueryParam('key')); let input = $state(router.getQueryParam('key'));
let projects = $state(null);
let tasks = $state(null); let tasks = $state(null);
async function setKey(ev){ async function setKey(ev){
@@ -38,6 +39,7 @@
body: JSON.stringify(data) body: JSON.stringify(data)
}; };
fetch(api('bookmark/search'),options).then(handleBookmarks); fetch(api('bookmark/search'),options).then(handleBookmarks);
fetch(api('project/search'),options).then(handleProjects);
fetch(api('task/search'),options).then(handleTasks); 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){ async function handleTasks(resp){
if (resp.ok){ if (resp.ok){
const res = await resp.json(); const res = await resp.json();
@@ -85,6 +96,20 @@
<button type="submit">{t('go')}</button> <button type="submit">{t('go')}</button>
</form> </form>
</fieldset> </fieldset>
{#if projects}
<fieldset>
<legend>
{t('projects')}
</legend>
<ul>
{#each Object.values(projects) as project}
<li>
<a href="#" onclick={e=>go(`/project/${project.id}/view`)} >{project.name}</a>
</li>
{/each}
</ul>
</fieldset>
{/if}
{#if tasks} {#if tasks}
<fieldset> <fieldset>
<legend> <legend>

View File

@@ -5,13 +5,18 @@ import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.core.model.Permission; import de.srsoftware.umbrella.core.model.Permission;
import de.srsoftware.umbrella.core.model.Project; import de.srsoftware.umbrella.core.model.Project;
import de.srsoftware.umbrella.core.model.Status; import de.srsoftware.umbrella.core.model.Status;
import java.util.Collection;
import java.util.List;
import java.util.Map; import java.util.Map;
public interface ProjectDb { public interface ProjectDb {
void dropMember(long projectId, long userId); void dropMember(long projectId, long userId);
Map<Long, Project> find(long userId, Collection<String> keys, boolean fulltext);
Map<Long, Permission> getMembers(Project project); Map<Long, Permission> getMembers(Project project);
Project load(long projectId) throws UmbrellaException; Project load(long projectId) throws UmbrellaException;
Map<Long, Project> ofCompany(long companyId, boolean includeClosed) throws UmbrellaException; Map<Long, Project> ofCompany(long companyId, boolean includeClosed) throws UmbrellaException;
Map<Long, Project> ofUser(long userId, boolean includeClosed) throws UmbrellaException; Map<Long, Project> ofUser(long userId, boolean includeClosed) throws UmbrellaException;
Project save(Project prj) throws UmbrellaException; Project save(Project prj) throws UmbrellaException;

View File

@@ -4,6 +4,7 @@ package de.srsoftware.umbrella.project;
import static de.srsoftware.umbrella.core.ConnectionProvider.connect; import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.Paths.LIST; 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.Util.mapValues;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
import static de.srsoftware.umbrella.core.model.Permission.*; import static de.srsoftware.umbrella.core.model.Permission.*;
@@ -29,12 +30,12 @@ import org.json.JSONObject;
public class ProjectModule extends BaseHandler implements ProjectService { public class ProjectModule extends BaseHandler implements ProjectService {
private final ProjectDb projects; private final ProjectDb projectDb;
public ProjectModule(ModuleRegistry registry, Configuration config) throws UmbrellaException { public ProjectModule(ModuleRegistry registry, Configuration config) throws UmbrellaException {
super(registry); super(registry);
var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingFieldException(CONFIG_DATABASE)); 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) { private void addMember(Project project, long userId) {
@@ -101,6 +102,7 @@ public class ProjectModule extends BaseHandler implements ProjectService {
var head = path.pop(); var head = path.pop();
return switch (head) { return switch (head) {
case LIST -> postProjectList(ex, user.get()); case LIST -> postProjectList(ex, user.get());
case SEARCH -> postSearch(user.get(),ex);
case null -> postProject(ex, user.get()); case null -> postProject(ex, user.get());
default -> { default -> {
var projectId = Long.parseLong(head); var projectId = Long.parseLong(head);
@@ -120,12 +122,12 @@ public class ProjectModule extends BaseHandler implements ProjectService {
private void dropMember(Project project, long userId) { 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"); 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); project.members().remove(userId);
} }
private boolean getProject(HttpExchange ex, long projectId, UmbrellaUser user) throws IOException, UmbrellaException { 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()); if (!project.hasMember(user)) throw forbidden("You are not a member of {0}",project.name());
var map = project.toMap(); 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(COMPANY,data));
@@ -133,7 +135,7 @@ public class ProjectModule extends BaseHandler implements ProjectService {
} }
public Map<Long, Project> listCompanyProjects(long companyId, boolean includeClosed) throws UmbrellaException { public Map<Long, Project> listCompanyProjects(long companyId, boolean includeClosed) throws UmbrellaException {
var projectList = projects.ofCompany(companyId, includeClosed); var projectList = projectDb.ofCompany(companyId, includeClosed);
loadMembers(projectList.values()); loadMembers(projectList.values());
return projectList; return projectList;
} }
@@ -147,7 +149,7 @@ public class ProjectModule extends BaseHandler implements ProjectService {
@Override @Override
public Map<Long, Project> listUserProjects(long userId, boolean includeClosed) throws UmbrellaException { public Map<Long, Project> listUserProjects(long userId, boolean includeClosed) throws UmbrellaException {
var projectMap = projects.ofUser(userId, includeClosed); var projectMap = projectDb.ofUser(userId, includeClosed);
loadMembers(projectMap.values()); loadMembers(projectMap.values());
return projectMap; return projectMap;
} }
@@ -159,14 +161,14 @@ public class ProjectModule extends BaseHandler implements ProjectService {
@Override @Override
public Project load(long projectId) { public Project load(long projectId) {
return projects.load(projectId); return projectDb.load(projectId);
} }
@Override @Override
public Collection<Project> loadMembers(Collection<Project> projectList) { public Collection<Project> loadMembers(Collection<Project> projectList) {
var userMap = new HashMap<Long,UmbrellaUser>(); var userMap = new HashMap<Long,UmbrellaUser>();
for (var project : projectList){ for (var project : projectList){
for (var entry : projects.getMembers(project).entrySet()){ for (var entry : projectDb.getMembers(project).entrySet()){
var userId = entry.getKey(); var userId = entry.getKey();
var permission = entry.getValue(); var permission = entry.getValue();
var user = userMap.computeIfAbsent(userId,k -> userService().loadUser(userId)); 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 { 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()); if (!project.hasMember(user)) throw forbidden("You are not a member of {0}",project.name());
var json = json(ex); var json = json(ex);
if (json.has(DROP_MEMBER) && json.get(DROP_MEMBER) instanceof Number id) dropMember(project,id.longValue()); 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(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()); 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()); 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(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(NAME) && json.get(NAME) instanceof String name)) throw missingFieldException(NAME);
var newState = new Status(name,code.intValue()); 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 { 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 owner = Map.of(user.id(),new Member(user,OWNER));
var prj = new Project(0,name,description, OPEN.code(),companyId,showClosed, owner, PREDEFINED); 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){ 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(); 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()); if (json.has(COMPANY_ID) && json.get(COMPANY_ID) instanceof Number companyId) return listCompanyProjects(ex, user, companyId.longValue());
return listUserProjects(ex,user,showClosed); 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));
}
} }

View File

@@ -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.core.model.Status.OPEN;
import static de.srsoftware.umbrella.project.Constants.*; import static de.srsoftware.umbrella.project.Constants.*;
import static java.lang.System.Logger.Level.ERROR; import static java.lang.System.Logger.Level.ERROR;
import static java.lang.System.Logger.Level.WARNING;
import static java.text.MessageFormat.format; import static java.text.MessageFormat.format;
import de.srsoftware.umbrella.core.BaseDb; 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 de.srsoftware.umbrella.core.model.Status;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -163,7 +165,24 @@ CREATE TABLE IF NOT EXISTS {0} (
} }
} }
@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));
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 @Override
public Map<Long, Project> ofUser(long userId, boolean includeClosed) throws UmbrellaException { public Map<Long, Project> ofUser(long userId, boolean includeClosed) throws UmbrellaException {

View File

@@ -121,9 +121,7 @@ CREATE TABLE IF NOT EXISTS {0} (
var query = select(ALL).from(TABLE_TASKS).leftJoin(ID,TABLE_TASKS_USERS,TASK_ID) var query = select(ALL).from(TABLE_TASKS).leftJoin(ID,TABLE_TASKS_USERS,TASK_ID)
.where(USER_ID,equal(userId)); .where(USER_ID,equal(userId));
for (var key : keys) query.where(NAME,like("%"+key+"%")); for (var key : keys) query.where(NAME,like("%"+key+"%"));
if (fulltext) { LOG.log(WARNING,"Full-text search not implemented for tasks");
for (var key : keys) query.where(DESCRIPTION,like("%"+key+"%"));
}
var rs = query.exec(db); var rs = query.exec(db);
while (rs.next()){ while (rs.next()){