working on patching vcards
This commit is contained in:
@@ -3,8 +3,13 @@ package de.srsoftware.umbrella.contact;
|
|||||||
|
|
||||||
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
||||||
import de.srsoftware.umbrella.core.model.Contact;
|
import de.srsoftware.umbrella.core.model.Contact;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public interface ContactDb {
|
public interface ContactDb {
|
||||||
Map<Long,Contact> listContactsOf(long id) throws UmbrellaException;
|
Map<Long,Contact> listContactsOf(long userId) throws UmbrellaException;
|
||||||
|
|
||||||
|
Contact load(long id, long userId);
|
||||||
|
|
||||||
|
Contact save(Contact contact);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,30 @@ public class ContactModule extends BaseHandler implements ContactService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean doPatch(Path path, HttpExchange ex) throws IOException {
|
||||||
|
addCors(ex);
|
||||||
|
try {
|
||||||
|
Optional<Token> token = SessionToken.from(ex).map(Token::of);
|
||||||
|
var user = userService().loadUser(token);
|
||||||
|
if (user.isEmpty()) return unauthorized(ex);
|
||||||
|
var head = path.pop();
|
||||||
|
try {
|
||||||
|
var id = Long.parseLong(head);
|
||||||
|
return patchContact(id, user.get(), ex);
|
||||||
|
} catch (Exception ignored){
|
||||||
|
return super.doPatch(path,ex);
|
||||||
|
}
|
||||||
|
} catch (UmbrellaException e) {
|
||||||
|
return send(ex,e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean patchContact(long id, UmbrellaUser user, HttpExchange ex) throws IOException {
|
||||||
|
var contact = contactDb.load(id, user.id()).patch(json(ex));
|
||||||
|
return sendContent(ex,contactDb.save(contact).toMap());
|
||||||
|
}
|
||||||
|
|
||||||
private boolean getContacts(UmbrellaUser user, HttpExchange ex) throws IOException {
|
private boolean getContacts(UmbrellaUser user, HttpExchange ex) throws IOException {
|
||||||
return sendContent(ex,mapValues(listContactsOf(user)));
|
return sendContent(ex,mapValues(listContactsOf(user)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import static de.srsoftware.tools.jdbc.Query.select;
|
|||||||
import static de.srsoftware.umbrella.contact.Constants.*;
|
import static de.srsoftware.umbrella.contact.Constants.*;
|
||||||
import static de.srsoftware.umbrella.core.Constants.*;
|
import static de.srsoftware.umbrella.core.Constants.*;
|
||||||
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.databaseException;
|
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.databaseException;
|
||||||
|
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.notFound;
|
||||||
|
import static java.lang.System.Logger.Level.ERROR;
|
||||||
import static java.text.MessageFormat.format;
|
import static java.text.MessageFormat.format;
|
||||||
|
|
||||||
import de.srsoftware.umbrella.core.BaseDb;
|
import de.srsoftware.umbrella.core.BaseDb;
|
||||||
@@ -72,4 +74,23 @@ public class SqliteDb extends BaseDb implements ContactDb{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Contact load(long id, long userId) {
|
||||||
|
try {
|
||||||
|
var rs = select(ALL).from(TABLE_CONTACTS).leftJoin(ID,TABLE_CONTACTS_USERS,CONTACT_ID).where(USER_ID,equal(userId)).where(ID,equal(id)).exec(db);
|
||||||
|
Contact contact = null;
|
||||||
|
if (rs.next()) contact = Contact.of(rs);
|
||||||
|
rs.close();
|
||||||
|
if (contact != null) return contact;
|
||||||
|
throw notFound("Failed to load contact with id = {0}",id);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw databaseException("Failed to load contacts of user {0}",userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Contact save(Contact contact) {
|
||||||
|
LOG.log(ERROR,"Save not implemented!");
|
||||||
|
return contact;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ public class Constants {
|
|||||||
public static final String FIELD_TYPE_PREFIX = "type_prefix";
|
public static final String FIELD_TYPE_PREFIX = "type_prefix";
|
||||||
public static final String FIELD_TYPE_SUFFIX = "type_suffix";
|
public static final String FIELD_TYPE_SUFFIX = "type_suffix";
|
||||||
public static final String FIELD_UNIT = "unit";
|
public static final String FIELD_UNIT = "unit";
|
||||||
|
public static final String FROM = "from";
|
||||||
public static final String FULLTEXT = "fulltext";
|
public static final String FULLTEXT = "fulltext";
|
||||||
|
|
||||||
public static final String GET = "GET";
|
public static final String GET = "GET";
|
||||||
@@ -168,6 +169,7 @@ public class Constants {
|
|||||||
public static final String THEME = "theme";
|
public static final String THEME = "theme";
|
||||||
public static final String TITLE = "title";
|
public static final String TITLE = "title";
|
||||||
public static final String TIMESTAMP = "timestamp";
|
public static final String TIMESTAMP = "timestamp";
|
||||||
|
public static final String TO = "to";
|
||||||
public static final String TOKEN = "token";
|
public static final String TOKEN = "token";
|
||||||
public static final String TOTAL_PRIO = "total_prio";
|
public static final String TOTAL_PRIO = "total_prio";
|
||||||
public static final String TYPE = "type";
|
public static final String TYPE = "type";
|
||||||
|
|||||||
@@ -2,8 +2,11 @@
|
|||||||
package de.srsoftware.umbrella.core.model;
|
package de.srsoftware.umbrella.core.model;
|
||||||
|
|
||||||
import static de.srsoftware.umbrella.core.Constants.*;
|
import static de.srsoftware.umbrella.core.Constants.*;
|
||||||
|
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException;
|
||||||
|
|
||||||
import de.srsoftware.tools.Mappable;
|
import de.srsoftware.tools.Mappable;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -17,4 +20,10 @@ public record Contact(long id, String vcard) implements Mappable {
|
|||||||
public Map<String, Object> toMap() {
|
public Map<String, Object> toMap() {
|
||||||
return Map.of(ID,id,VCARD,vcard);
|
return Map.of(ID,id,VCARD,vcard);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Contact patch(JSONObject json) {
|
||||||
|
if (!(json.get(FROM) instanceof String from)) throw missingFieldException(FROM);
|
||||||
|
if (!(json.get(TO) instanceof String to)) throw missingFieldException(TO);
|
||||||
|
return new Contact(id,vcard.replace(from, to));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import { t } from '../../translations.svelte';
|
import { api } from '../../urls.svelte';
|
||||||
|
import { error, yikes } from '../../warn.svelte';
|
||||||
|
import { t } from '../../translations.svelte';
|
||||||
|
|
||||||
|
|
||||||
import Address from './Address.svelte';
|
import Address from './Address.svelte';
|
||||||
import Email from './Email.svelte';
|
import Email from './Email.svelte';
|
||||||
@@ -12,9 +15,28 @@
|
|||||||
|
|
||||||
let addresses = $derived(contact.vcard.match(/^ADR.*:.+$/gm));
|
let addresses = $derived(contact.vcard.match(/^ADR.*:.+$/gm));
|
||||||
let code = $state(false);
|
let code = $state(false);
|
||||||
|
let fns = $derived(contact.vcard.match(/^FN.*:.+$/gm));
|
||||||
let emails = $derived(contact.vcard.match(/^EMAIL.*:.+$/gm));
|
let emails = $derived(contact.vcard.match(/^EMAIL.*:.+$/gm));
|
||||||
let extra_fields = $derived(contact.vcard.match(/^X-.*:.+/gm));
|
let extra_fields = $derived(contact.vcard.match(/^X-.*:.+/gm));
|
||||||
|
|
||||||
|
async function patch(from,to){
|
||||||
|
console.log(`patch(${contact.id}: ${from} → ${to})`);
|
||||||
|
if (from == to) return;
|
||||||
|
const url = api(`contact/${contact.id}`);
|
||||||
|
const res = await fetch(url,{
|
||||||
|
method:'PATCH',
|
||||||
|
credentials:'include',
|
||||||
|
body:JSON.stringify({from:from,to:to})
|
||||||
|
});
|
||||||
|
if (res.ok){
|
||||||
|
yikes();
|
||||||
|
contact = await res.json();
|
||||||
|
} else {
|
||||||
|
error(res);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function toggleCode(){
|
function toggleCode(){
|
||||||
code = !code;
|
code = !code;
|
||||||
}
|
}
|
||||||
@@ -23,14 +45,16 @@
|
|||||||
<fieldset class="vcard">
|
<fieldset class="vcard">
|
||||||
<legend>
|
<legend>
|
||||||
{t('contact_number',{number:contact.id})}
|
{t('contact_number',{number:contact.id})}
|
||||||
<button class="symbol" onclick={toggleCode}>C</button>
|
<button class="symbol" onclick={toggleCode}></button>
|
||||||
</legend>
|
</legend>
|
||||||
<table>
|
<table>
|
||||||
<thead></thead>
|
<thead></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<FN vcard={contact.vcard} />
|
{#each fns as code}
|
||||||
|
<FN {code} {patch} />
|
||||||
|
{/each}
|
||||||
</td>
|
</td>
|
||||||
{#if code}
|
{#if code}
|
||||||
<td rowspan="6">
|
<td rowspan="6">
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import LineEditor from '../../Components/LineEditor.svelte';
|
||||||
import { fn } from '../../vcard.js';
|
import { fn } from '../../vcard.js';
|
||||||
import { t } from '../../translations.svelte';
|
import { t } from '../../translations.svelte';
|
||||||
|
|
||||||
let { vcard } = $props();
|
let { code, patch = (from, to) => true } = $props();
|
||||||
|
|
||||||
let name = $derived(fn(vcard));
|
let name = $derived(fn(code));
|
||||||
|
|
||||||
|
function onSet(newVal){
|
||||||
|
const newCode = code.replace(name,newVal);
|
||||||
|
return patch(code,newCode);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if name}
|
{#if name}
|
||||||
<span class="formatted name">{name}</span>
|
<LineEditor type="span" editable={true} value={name} {onSet} />
|
||||||
{/if}
|
{/if}
|
||||||
Reference in New Issue
Block a user