improving usability by proposing tags

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
2026-04-23 10:37:50 +02:00
parent db606dd20e
commit b5e8979ed9
5 changed files with 111 additions and 33 deletions
@@ -12,6 +12,8 @@ public interface AccountDb {
Collection<Account> listAccounts(long userId);
Collection<String> listTags(long accountId, String source, String destination);
Account loadAccount(long accountId);
Transaction loadTransaction(long transactionId);
@@ -156,6 +156,25 @@ public class AccountingModule extends BaseHandler implements AccountingService {
return sendContent(ex, transaction);
}
private List<String> egalize(Set<String> tags, String key) {
var result = new HashSet<String>();
var lower = key.toLowerCase();
var len = key.length();
for (var tag : tags) {
if (tag.length() == key.length()) continue;
result.add(tag.toLowerCase().startsWith(lower) ? key + tag.substring(len) : tag);
}
return result.stream().sorted(String.CASE_INSENSITIVE_ORDER).toList();
}
private String extractParty(String field, JSONObject json){
if (!json.has(field)) return null;
if (!(json.get(field) instanceof JSONObject data)) return null;
if (data.has(Field.ID)) return data.get(Field.ID).toString();
if (data.has(Field.DISPLAY)) return data.get(Field.DISPLAY).toString();
return null;
}
private boolean getAccount(UmbrellaUser user, long accountId, HttpExchange ex) throws IOException {
LOG.log(WARNING,"Missing authorization check in AccountingModule.getAccount(…)!");
var account = accountDb.loadAccount(accountId);
@@ -294,20 +313,17 @@ public class AccountingModule extends BaseHandler implements AccountingService {
private boolean postSearchTags(long accountId, UmbrellaUser user, HttpExchange ex) throws IOException {
LOG.log(WARNING,"Missing authorization check in AccountingModule.getAccount(…)!");
var key = body(ex);
var tags = accountDb.searchTagsContaining(key,accountId);
if (tags.size()<10) tags.addAll(tagService().search(key,user));
return sendContent(ex,egalize(tags,key));
}
private List<String> egalize(Set<String> tags, String key) {
var result = new HashSet<String>();
var lower = key.toLowerCase();
var len = key.length();
for (var tag : tags) {
if (tag.length() == key.length()) continue;
result.add(tag.toLowerCase().startsWith(lower) ? key + tag.substring(len) : tag);
if (!key.trim().startsWith("{")) { // search tags that contain value of body
var tags = accountDb.searchTagsContaining(key, accountId);
if (tags.size() < 10) tags.addAll(tagService().search(key, user));
return sendContent(ex, egalize(tags, key));
}
return result.stream().sorted(String.CASE_INSENSITIVE_ORDER).toList();
// search tags for account with specified source and destination
var json = new JSONObject(key);
var src = extractParty(Field.SOURCE, json);
var dst = extractParty(Field.DESTINATION, json);
var tags = accountDb.listTags(accountId,src,dst);
return sendContent(ex,tags);
}
private boolean postToAccount(long accountId, Path path, UmbrellaUser user, HttpExchange ex) throws IOException {
@@ -150,6 +150,45 @@ public class SqliteDb extends BaseDb implements AccountDb {
}
}
@Override
public Collection<String> listTags(long accountId, String source, String destination) {
try {
var rs = select(TRANSACTION_ID,Field.TAG)
.from(TABLE_TRANSACTIONS)
.leftJoin(ID,TABLE_TAGS_TRANSACTIONS, TRANSACTION_ID)
.leftJoin(TAG_ID,TABLE_TAGS,ID)
.where(ACCOUNT,equal(accountId))
.where(SOURCE,equal(source))
.where(DESTINATION,equal(destination))
.sort(TRANSACTION_ID).exec(db);
Set<String> set = null;
Set<String> transactionTags = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
Long lastTransaction = null;
while (rs.next()){
var currentTransaction = rs.getLong(TRANSACTION_ID);
if (lastTransaction == null) { // first row
transactionTags.add(rs.getString(TAG));
lastTransaction = currentTransaction;
} else if (lastTransaction == currentTransaction) {
transactionTags.add(rs.getString(TAG));
} else {
if (set == null) {
set = transactionTags;
} else {
set.retainAll(transactionTags);
}
transactionTags = new HashSet<>();
transactionTags.add(rs.getString(TAG));
lastTransaction = currentTransaction;
}
}
rs.close();
return set;
} catch (SQLException e){
throw failedToLoadMembers(accountId);
}
}
@Override
public Account loadAccount(long accountId) {
try {