Skip to content

Commit

Permalink
Refactor `guid demote its use.
Browse files Browse the repository at this point in the history
`guid` was originally envisioned as a semantic ID (hash of an entry) to
make mapping of entries easier for bulk importers.
This is unnecessary and superfluous as external importers can maintain
such maps externally without having to bring over that layer into the
DB.

This refactor changes `guid` from a semantic hash to a random UUID
and replaces its use in queries with numeric primary keys. The `guid`
field will only be used in public APIs such as new entry and correction
submissions.
  • Loading branch information
knadh committed Dec 5, 2021
1 parent 9c7f0ab commit afe26ee
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 156 deletions.
2 changes: 1 addition & 1 deletion admin/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<fieldset class="row">
<div class="column four">
<select name="from_lang" x-model="fromLang">
<option value="*guid">*GUID</option>
<option value="*id">*ID</option>
<template x-for="[id, l] in Object.entries(config.languages)" :key="id">
<option :value="id" x-text="l.name" x-bind:selected="id === fromLang"></option>
</template>
Expand Down
13 changes: 3 additions & 10 deletions admin/entry.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,7 @@ <h3 x-text="isNew ? 'New entry' : 'Edit entry'"></h3>
<span class="help">First letter</span>
</div>
</fieldset>
<template x-if="isNew">
<fieldset>
<label>GUID</label>
<input type="text" name="guid" x-model="entry.guid" />
<span class="help">Leave empty to auto-generate.</span>
</fieldset>
</template>

<p>
<a href="" @click.prevent="onToggleOptions">
<span x-text="!isFormOpen ? 'More options +' : 'Hide options -'"></span>
Expand Down Expand Up @@ -91,10 +85,9 @@ <h3 x-text="isNew ? 'New entry' : 'Edit entry'"></h3>

<template x-if="!isNew">
<ul class="no meta">
<li><label>ID</label> <span x-text="entry.id"></span></li>
<li><label>Created</label> <span x-text="entry.created_at.slice(0, 16).replace('T', ' ')"></span></li>
<li><label>Updated</label> <span x-text="entry.updated_at.slice(0, 16).replace('T', ' ')"></span></li>
<li><label>GUID</label> <span x-text="entry.guid"></span></li>
<li><label>ID</label> <span x-text="entry.id"></span></li>
</ul>
</template>

Expand All @@ -105,7 +98,7 @@ <h3>Parent entries (<span x-text="parentEntries.length"></span>)</h3>
<template x-for="r in parentEntries" :key="r.id">
<li class="rel">
<p>
<a x-bind:href="makeURL({'guid': r.guid})" x-text="r.content" class="content"></a>
<a x-bind:href="makeURL({'id': r.id})" x-text="r.content" class="content"></a>
<template x-if="r.phones.length > 0">
<span class="phones" x-text="r.phones.join(', ')"></span>
</template>
Expand Down
4 changes: 2 additions & 2 deletions admin/relation.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ <h3>Edit relation</h3>
<li><label>Created</label> <span x-text="entry.relation.created_at.slice(0, 16).replace('T', ' ')"></span></li>
<li><label>Updated</label> <span x-text="entry.relation.created_at.slice(0, 16).replace('T', ' ')"></span></li>
<li><label>Relation ID</label> <span x-text="entry.relation.id"></span></li>
<li><label>Parent GUID</label> <span x-text="entry.parent.guid"></span></li>
<li><label>Child GUID</label> <span x-text="entry.guid"></span></li>
<li><label>Parent ID</label> <span x-text="entry.parent.id"></span></li>
<li><label>Definition ID</label> <span x-text="entry.id"></span></li>
</ul>
</div>
</template>
Expand Down
22 changes: 11 additions & 11 deletions admin/search.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,35 @@
{{ template "header" . }}

<section x-data="searchResultsComponent()" x-init="onLoad" @search.window="onSearch">
<template x-if="!guid && loading['entries.search'] !== true">
<template x-if="!id && loading['entries.search'] !== true">
<h3><span x-text="total"></span> results for &ldquo;<span x-text="query"></span>&rdquo;</h3>
</template>
<template x-if="loading['entries.search'] === true">
<span class="loading"></span>
</template>

<ol class="entries">
<template x-for="e in entries" :key="e.guid">
<template x-for="e in entries" :key="e.id">
<li class="entry box">
<div class="float-right actions">
<a x-bind:href="makeURL({guid: e.guid})" @click.prevent="onEditEntry(e)">Edit</a>
<a href="#" @click.prevent="onDeleteEntry(e.guid)">Delete</a>
<a x-bind:href="makeURL({id: e.id})" @click.prevent="onEditEntry(e)">Edit</a>
<a href="#" @click.prevent="onDeleteEntry(e.id)">Delete</a>
</div>

<h3 class="heading">
<a x-bind:href="makeURL({guid: e.guid})" @click.prevent="onEditEntry(e)" x-text="e.content"></a>
<a x-bind:href="makeURL({id: e.id})" @click.prevent="onEditEntry(e)" x-text="e.content"></a>
</h3>
<div class="phones"><span class="pronun" x-text="e.phones.join(', ')"></span></div>

<ol class="relations">
<template x-for="(r, n) in e.relations" :key="r.id">
<li class="rel" :class="{ highlight: order[e.guid] && order[e.guid].changedRels[r.guid] }">
<li class="rel" :class="{ highlight: order[e.id] && order[e.id].changedRels[r.id] }">
<p><span class="types" x-text="r.relation.types"></span> <span x-text="r.content" class="content"></span></p>
<p class="actions">
<a href="#" @click.prevent="onDetatchRelation(e.guid, r.guid)">Detatch</a>
<a x-bind:href="`${_urls.admin}/entries/${r.guid}?parent=${e.guid}`" @click.prevent="onEditRelation(r, e)">Edit relation</a>
<a x-bind:href="`${_urls.admin}/entries/${r.guid}?parent=${e.guid}`" @click.prevent="onEditEntry(r, e)">Edit entry</a>
<a x-bind:href="`${_urls.admin}/entries/${r.guid}?parent=${e.guid}`" @click.prevent="onDeleteRelationEntry(r.guid)">Delete entry</a>
<a href="#" @click.prevent="onDetatchRelation(e.id, r.id)">Detatch</a>
<a x-bind:href="`${_urls.admin}/entries/${r.id}?parent=${e.id}`" @click.prevent="onEditRelation(r, e)">Edit relation</a>
<a x-bind:href="`${_urls.admin}/entries/${r.id}?parent=${e.id}`" @click.prevent="onEditEntry(r, e)">Edit entry</a>
<a x-bind:href="`${_urls.admin}/entries/${r.id}?parent=${e.id}`" @click.prevent="onDeleteRelationEntry(r.id)">Delete entry</a>
<span>
<a href="#" @click.prevent="onReorderRelation(e, r, n, -1)" title="Move up">&uarr;</a>
<a href="#" @click.prevent="onReorderRelation(e, r, n, 1)" title="Move down">&darr;</a>
Expand All @@ -40,7 +40,7 @@ <h3 class="heading">
</template>
</ol>
<p><a href="" @click.prevent="onAddDefinition(e)">+ Add definition</a>
<template x-if="order[e.guid]">
<template x-if="order[e.id]">
<div class="float-right">
<button class="button-outline" @click.prevent="onResetRelationOrder(e)">Reset</button>
<button class="button" @click.prevent="onSaveRelationOrder(e)">Save order</button>
Expand Down
72 changes: 36 additions & 36 deletions admin/static/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ function globalComponent() {
makeURL(p) {
const u = new URLSearchParams();

if (p.guid) {
u.set("guid", p.guid);
if (p.id) {
u.set("id", p.id);
}
if (p.fromLang) {
u.set("from_lang", p.fromLang);
Expand Down Expand Up @@ -123,7 +123,7 @@ function homeComponent() {
// Search form component.
function searchFormComponent() {
return {
fromLang: localStorage.fromLang || '*guid',
fromLang: localStorage.fromLang || '*id',
toLang: '',
query: '',

Expand All @@ -136,26 +136,26 @@ function searchFormComponent() {
localStorage.fromLang = this.fromLang;
localStorage.toLang = this.toLang;

// If GUID is selected, redirect to the GUID page. Otherwise, let the form normally submit.
if (this.fromLang === '*guid') {
// If id is selected, redirect to the id page. Otherwise, let the form normally submit.
if (this.fromLang === '*id') {
e.preventDefault();
document.location.href = `${_urls.admin}/search?guid=${this.query}`;
document.location.href = `${_urls.admin}/search?id=${this.query}`;
}
}
}
}

function searchResultsComponent() {
return {
guid: null,
id: null,
query: null,
fromLang: null,
toLang: null,
total: 0,
entries: [],
hasEntryReordered: {},

// entryi-guid -> { changedRels{}, originalOrder[] }
// entry-id -> { changedRels{}, originalOrder[] }
order: {},
hasRelReordered: {},

Expand All @@ -166,11 +166,11 @@ function searchResultsComponent() {
onSearch() {
// Query params.
const q = new URLSearchParams(document.location.search);
[this.guid, this.query, this.fromLang, this.toLang] = [q.get("guid"), q.get("query"), q.get("from_lang"), q.get("to_lang")];
[this.id, this.query, this.fromLang, this.toLang] = [q.get("id"), q.get("query"), q.get("from_lang"), q.get("to_lang")];

// Fetch one entry buy GUID.
if (this.guid) {
this.api('entries.get', `/entries/${this.guid}`).then((data) => {
// Fetch one entry by id.
if (this.id) {
this.api('entries.get', `/entries/${this.id}`).then((data) => {
this.entries = [data];
})
} else if (this.fromLang && this.query) {
Expand All @@ -194,33 +194,33 @@ function searchResultsComponent() {
return;
}

if (!this.order.hasOwnProperty(entry.guid)) {
this.order[entry.guid] = { original: [...entry.relations], changedRels: {} }
if (!this.order.hasOwnProperty(entry.id)) {
this.order[entry.id] = { original: [...entry.relations], changedRels: {} }
}

this.order[entry.guid].changedRels[rel.guid] = true;
this.order[entry.id].changedRels[rel.id] = true;
const i = entry.relations.splice(n, 1)[0];
entry.relations.splice(n + d, 0, i);
},

onResetRelationOrder(entry) {
entry.relations = [...this.order[entry.guid].original];
delete (this.order[entry.guid]);
entry.relations = [...this.order[entry.id].original];
delete (this.order[entry.id]);
},

onSaveRelationOrder(entry) {
const ids = entry.relations.map((r) => r.relation.id);
this.api('entries.reorder', `/entries/${entry.guid}/relations/weights`, 'PUT', ids).then(() => {
delete (this.order[entry.guid]);
this.api('entries.reorder', `/entries/${entry.id}/relations/weights`, 'PUT', ids).then(() => {
delete (this.order[entry.id]);
});
},

onDetatchRelation(fromGuid, toGuid) {
onDetatchRelation(fromId, toId) {
if (!confirm("Detatch this definition from the above entry? It will not be deleted from the database and may still be attached to other entries.")) {
return;
}

this.api('relations.detatch', `/entries/${fromGuid}/relations/${toGuid}`, 'DELETE').then(() => this.onSearch());
this.api('relations.detatch', `/entries/${fromId}/relations/${toId}`, 'DELETE').then(() => this.onSearch());
},

onEditEntry(entry, parent) {
Expand All @@ -243,18 +243,18 @@ function searchResultsComponent() {
});
},

onDeleteEntry(guid) {
onDeleteEntry(id) {
if (!confirm("Delete this entry? The definitions are not deleted and may be attached to other entries.")) {
return;
}
this.api('entries.delete', `/entries/${guid}`, 'DELETE').then(() => this.onSearch());
this.api('entries.delete', `/entries/${id}`, 'DELETE').then(() => this.onSearch());
},

onDeleteRelationEntry(guid) {
onDeleteRelationEntry(id) {
if (!confirm("Delete this entry? It will be deleted from all entries in the database it may be attached to.")) {
return;
}
this.api('relations.delete', `/entries/${guid}`, 'DELETE').then(() => this.onSearch());
this.api('relations.delete', `/entries/${id}`, 'DELETE').then(() => this.onSearch());
}
}
}
Expand All @@ -281,15 +281,15 @@ function entryComponent() {
tokens: data.tokens.split(' ').join('\n')
};
this.parentEntries = [];
this.isNew = !this.entry.guid ? true : false;
this.isNew = !this.entry.id ? true : false;
this.isVisible = true;

this.$nextTick(() => {
this.$refs.content.focus();
});

if (this.entry.parent) {
this.getParentEntries(this.entry.guid);
this.getParentEntries(this.entry.id);
}
},

Expand Down Expand Up @@ -327,10 +327,10 @@ function entryComponent() {
if (this.isNew) {
this.api('entries.create', `/entries`, 'POST', data).then((data) => {
this.onClose()
document.location.href = `${_urls.admin}/search?guid=${data.guid}`;
document.location.href = `${_urls.admin}/search?id=${data.id}`;
});
} else {
this.api('entries.update', `/entries/${this.entry.guid}`, 'PUT', data).then(() => this.onClose());
this.api('entries.update', `/entries/${this.entry.id}`, 'PUT', data).then(() => this.onClose());
}
},

Expand All @@ -340,14 +340,14 @@ function entryComponent() {
return;
}

this.api('entries.delete', `/entries/${this.entry.guid}`, 'DELETE').then(() => {
this.api('entries.delete', `/entries/${this.entry.id}`, 'DELETE').then(() => {
this.onClose();
});
},


getParentEntries(guid) {
this.api('entries.getParents', `/entries/${guid}/parents`).then((data) => {
getParentEntries(id) {
this.api('entries.getParents', `/entries/${id}/parents`).then((data) => {
this.parentEntries = data;
});
}
Expand Down Expand Up @@ -384,7 +384,7 @@ function relationComponent() {
notes: this.entry.relation.notes
};

this.api('relations.update', `/entries/${this.entry.parent.guid}/relations/${this.entry.relation.id}`, 'PUT', data).then(() => {
this.api('relations.update', `/entries/${this.entry.parent.id}/relations/${this.entry.relation.id}`, 'PUT', data).then(() => {
this.onClose();
this.$dispatch('search');
});
Expand Down Expand Up @@ -416,7 +416,7 @@ function definitionComponent() {
},

onSave() {
const data = {
const params = {
content: this.def.content,
initial: this.def.content[0].toUpperCase(),
lang: this.def.lang,
Expand All @@ -426,14 +426,14 @@ function definitionComponent() {
};

// Insert the definition entry first.
this.api(`/entries`, 'POST', data).then((data) => {
this.api('entries.create', `/entries`, 'POST', params).then((data) => {
// Add the relation.
const rel = {
types: this.def.types,
tags: linesToList(this.def.tags),
notes: this.def.notes,
};
api('relations.add', `/entries/${this.parent.guid}/relations/${data.guid}`, 'POST', rel).then(() => {
this.api('relations.add', `/entries/${this.parent.id}/relations/${data.id}`, 'POST', rel).then(() => {
this.$dispatch('search');
this.onClose();
});
Expand Down
Loading

0 comments on commit afe26ee

Please sign in to comment.