Skip to content

Commit

Permalink
GH-34: Added BadgerDB middleware. Create book-collection-badgerdb exa…
Browse files Browse the repository at this point in the history
…mple.

Refactored the way handler Options are done, which might cause breaking changes.
  • Loading branch information
jirenius committed May 24, 2019
1 parent 0c85c52 commit 7f8ab16
Show file tree
Hide file tree
Showing 21 changed files with 1,286 additions and 145 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Expand Up @@ -10,4 +10,6 @@ debug
*.test

# Output of the go coverage tool
*.out
*.out

.vscode
3 changes: 3 additions & 0 deletions examples/book-collection-badgerdb/.gitignore
@@ -0,0 +1,3 @@
*.exe
/book-collection
/db
79 changes: 79 additions & 0 deletions examples/book-collection-badgerdb/README.md
@@ -0,0 +1,79 @@
# Book Collection BadgerDB example

This is an example RES service with an editable list of book authors and titles, persisted in BadgerDB.
* It exposes a collection, `library.books`, containing book model references.
* It exposes book models, `library.book.<BOOK_ID>`, of each book.
* It allows setting the books' *title* and *author* property through the `set` method.
* It allows creating new books that are added to the collection with the `new` method.
* It allows deleting existing books from the collection with the `delete` method.
* It verifies that a *title* and *author* is always set.
* It persists all changes to BadgerDB.

## Prerequisite

* Have [NATS Server](https://nats.io/download/nats-io/gnatsd/) and [Resgate](https://github.com/resgateio/resgate) running

## Install and run

Clone go-res repository and run example:
```bash
git clone https://github.com/jirenius/go-res
cd go-res/examples/book-collection
go run main.go
```

Open the client
```
http://localhost:8083
```

## Things to try out

**Realtime updates**
Run the client in two separate tabs to observe realtime updates.

**System reset**
Run the client and make some changes. Restart the node.js server to observe resetting of resources in clients.

**Resynchronization**
Run the client on two separate devices. Disconnect one device, then make changes with the other. Reconnect the first device to observe resynchronization.


## Web resources

### Get book collection
```
GET http://localhost:8080/api/library/books
```

### Get book
```
GET http://localhost:8080/api/library/book/<BOOK_ID>
```

### Update book properties
```
POST http://localhost:8080/api/library/book/<BOOK_ID>/set
```
*Body*
```
{ "title": "Animal Farming" }
```

### Add new book
```
POST http://localhost:8080/api/library/books/add
```
*Body*
```
{ "title": "Dracula", "author": "Bram Stoker" }
```

### Delete book
```
POST http://localhost:8080/api/library/books/delete
```
*Body*
```
{ "id": <BOOK_ID> }
```
178 changes: 178 additions & 0 deletions examples/book-collection-badgerdb/index.html
@@ -0,0 +1,178 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Book Collection Example</title>
<link rel="icon" href="data:,">
<script src="https://unpkg.com/resclient@latest/dist/resclient.min.js"></script>
<script src="https://unpkg.com/modapp-base-component@latest/dist/modapp-base-component.min.js"></script>
<script src="https://unpkg.com/modapp-resource-component@latest/dist/modapp-resource-component.min.js"></script>
<style>
body {
background: #eee;
font-family: Arial, Helvetica, sans-serif;
padding: 0;
margin: 0;
}
header {
background: #000;
color: #fff;
padding: 16px 1em;
}
h1 {
margin: 0;
padding: 0;
line-height: 32px;
font-size: 24px;
}
.top {
margin: 1em 1em;
}
.shadow {
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
}
#books {
max-width: 800px;
margin: 0 1em;
}
.new-container { margin: 1em 0; }
label {
margin: 0.5em 1em 0.5em 0;
font-weight: bold;
}
.new-container input { margin-right: 1em; }
#error-msg { color: #800 }
button {
display: inline-block;
border: none;
background: none;
color: #006;
}
button:hover { background: rgba(0,0,0,0.12); }
.list-item { padding: 8px 0; }
.card {
background: #fff;
padding: 1em 1em;
box-sizing: border-box;
}
.action { float: right }
.editing > .card { background: #eaeaff; }
.card > .edit { display: none; }
.editing > .card > .edit { display: inherit; }
.editing > .card > .view { display: none; }
.card h3 { margin: 0 0 8px 0; }
.card .author { font-style: italic; }
.label {
display: inline-block;
width: 80px;
font-weight: bold;
}
.edit-input + .edit-input { margin-top: 4px; }
</style>
</head>
<body>
<header class="shadow">
<h1>Book Collection Example</h1>
</header>
<div class="top">
<div class="new-container">
<label for="new-title">Title</label><input id="new-title" />
<label for="new-author">Author</label><input id="new-author" />
<button id="add-new">Add new</button>
</div>
<div id="error-msg"></div>
</div>
<hr>
<div id="books"></div>
<script>
const { Elem, Txt, Button, Input } = window["modapp-base-component"];
const { CollectionList, ModelTxt } = window["modapp-resource-component"];
const ResClient = resclient.default;

let client = new ResClient('ws://localhost:8080');

// Error handling
let errMsg = new Txt();
let errTimer = null;
errMsg.render(document.getElementById('error-msg'));
let showError = (err) => {
errMsg.setText(err && err.message ? err.message : String(err));
clearTimeout(errTimer);
errTimer = setTimeout(() => errMsg.setText(''), 7000);
};

// Add new click callback
document.getElementById('add-new').addEventListener('click', () => {
let newTitle = document.getElementById('new-title');
let newAuthor = document.getElementById('new-author');
client.call('library.books', 'new', {
title: newTitle.value,
author: newAuthor.value
}).then(() => {
// Clear values on successful add
newTitle.value = "";
newAuthor.value = "";
}).catch(showError);
});

// Get the collection from the service.
client.get('library.books').then(books => {
// Here we use modapp components to render the view.
// It is a protest against all these frameworks with virtual doms.
// Why use virtual doms when it is faster with vanilla javascript?
new Elem(n =>
n.component(new CollectionList(books, book => {
let c = new Elem(n =>
n.elem('div', { className: 'list-item' }, [
n.elem('div', { className: 'card shadow' }, [
n.elem('div', { className: 'view' }, [
n.elem('div', { className: 'action' }, [
n.component(new Button(`Edit`, () => {
c.getNode('titleInput').setValue(book.title);
c.getNode('authorInput').setValue(book.author);
c.addClass('editing');
})),
n.component(new Button(`Delete`, () => books.call('delete', { id: book.id }).catch(showError)))
]),
n.elem('div', { className: 'title' }, [
n.component(new ModelTxt(book, book => book.title, { tagName: 'h3'}))
]),
n.elem('div', { className: 'author' }, [
n.component(new Txt("By ")),
n.component(new ModelTxt(book, book => book.author))
])
]),
n.elem('div', { className: 'edit' }, [
n.elem('div', { className: 'action' }, [
n.component(new Button(`OK`, () => {
book.set({
title: c.getNode('titleInput').getValue(),
author: c.getNode('authorInput').getValue()
})
.then(() => c.removeClass('editing'))
.catch(showError);
})),
n.component(new Button(`Cancel`, () => c.removeClass('editing')))
]),
n.elem('div', { className: 'edit-input' }, [
n.component(new Txt("Title", { className: 'label' })),
n.component('titleInput', new Input())
]),
n.elem('div', { className: 'edit-input' }, [
n.component(new Txt("Author", { className: 'label' })),
n.component('authorInput', new Input())
])
])
])
])
);
return c;
}, { className: 'list' }))
).render(document.getElementById('books'));
}).catch(err => showError(err.code === 'system.connectionError'
? "Connection error. Are NATS Server and Resgate running?"
: err
));
</script>
</body>
</html>

0 comments on commit 7f8ab16

Please sign in to comment.