Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Facades #28

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/views/layouts/default.jade
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ html(ng-app="maki")
include ../partials/navbar

.contained
.ui.grid
include ../partials/flash

.ui.grid
.column.content(data-for="viewport")

include ../partials/flash

block content

footer
Expand Down
19 changes: 12 additions & 7 deletions app/views/partials/flash.jade
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
if (typeof(flash) != 'undefined')
.messages
.ui.row
if (typeof(flash.error) != 'undefined')
for error in flash.error
.alert.alert-error.alert-dismissable
button.close(type="button", data-dismiss="alert", aria-hidden="true") ×
| !{error}
.ui.error.message
i.icon.close(type="button", data-dismiss="alert")
p.header !{error}
if (typeof(flash.info) != 'undefined')
for info in flash.info
.alert.alert-info.alert-dismissable
button.close(type="button", data-dismiss="alert") ×
| !{info}
.ui.info.message
i.icon.close(type="button", data-dismiss="alert")
p.header !{info}
if (typeof(flash.success) != 'undefined')
for success in flash.success
.ui.success.message
i.icon.close(type="button", data-dismiss="alert")
p.header !{success}
151 changes: 120 additions & 31 deletions examples/bitfaucet/bitfaucet.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
var config = require('../../config');
// BitFaucet is a simple example of a Maki-powered application.
// It implements some basic Resources with external lookups, and overrides some
// of Maki's built-in behaviors.

// First, we'll retrieve the Maki class.
// In a real application, this could simply be `require('maki')`.
var Maki = require('../../lib/Maki');
var maki = new Maki( config );
// Create an instance of Maki – named according to our app.
// As shorthand, this could be `var bitfaucet = require('maki')();`
var bitfaucet = new Maki();

// Let's define a few requirements.
// In our case, we need a Bitcoin client.
var bitcoin = require('bitcoin');

var Faucet = maki.define('Faucet', {
// ## Resource Definitions
// Maki starts with definitions of "Resources" that your application exposes.
// Our first Resource is, well, a "Faucet".
var Faucet = bitfaucet.define('Faucet', {
// ### Resource Attributes
attributes: {
// Attributes can have enforced types, validators, behaviors...
// Most of these should be self-descriptive. For a full reference, check
// [the documentation]().
name: { type: String , default: 'Default' , required: true },
balance: { type: String , default: 0 , required: true },
host: { type: String , default: 'localhost', required: true },
Expand All @@ -15,6 +30,10 @@ var Faucet = maki.define('Faucet', {
pass: { type: String , default: 'default' },
timeout: { type: Number , default: 30000 }
},
// ### Resource Methods
// Maki Resources can expose "methods". These exist as functions on every
// instance of such a resource.
// In the case of a "Faucet", each faucet needs a bitcoind instance.
methods: {
btcClient: function() {
var faucet = this;
Expand All @@ -23,28 +42,98 @@ var Faucet = maki.define('Faucet', {
}
});

var Pour = maki.define('Pour', {
// We're also going to create another Resource, a "Pour".
// When a user wants to take money from the Faucet, they want to "create a new
// pour".
var Pour = bitfaucet.define('Pour', {
name: 'Pour',
attributes: {
_faucet: { type: maki.mongoose.SchemaTypes.ObjectId , required: true },
// **Our first "special" attribute!**
// This is a special type, an ObjectId, which is a "pointer" (like in C++)
// to another Resource.
//
// As you might imagine, a Pour cannot be created without a Faucet to Pour
// from.
_faucet: { type: bitfaucet.mongoose.SchemaTypes.ObjectId , required: true },
address: { type: String , max: 80 },
amount: { type: String , max: 80 },
ip: { type: String , private: true },
date: { type: Date , default: Date.now , restricted: true },
txid: { type: String },
comment: { type: String },
status: { type: String , enum: [
'pending',
'broadcast',
'failed'
], default: 'pending', required: true }
},
// ### Resource Handlers
// Handlers can be used to override internal behaviors for specific contexts.
// In the case of the Faucet, we want to change the default behavior for the
// 'create' method of a Pour, but _only_ for the `html` transformer. More
// detail on this later.
handlers: {
'html': {
create: function( req , res , next) {
console.log('handler called');

req.flash('success', 'Pour created successfully!');
return res.redirect('/');
}
}
}
});

// The Index Resource has a few extra custom options provided.
var Index = bitfaucet.define('Index', {
name: 'Index',
// First, we're going to override the default route for the `query` method.
// By default, this would be `/indices` due to Maki's semantics, but we want
// the Index resource to simply replace the front page.
routes: {
query: '/'
},
// Let's also change the default template (again, defaulting to
// `indices.jade`)
templates: {
query: 'index'
},
// ### Resource Requirements
// Requirement are additional Resources that must be collected when rendering
// non-pure (i.e., non-JSON) views. For example, when rendering the Index,
// we'll need to collect a Faucet.
requires: {
// Requirements are key->value pairs, where key is a defined (!) resource,
// and the pair is an hashmap with some expected values.
'Faucet': {
// `filter` defines what query to pass to the Resource engine.
filter: {},
// `single` tells Maki that we are collecting exactly one instance of this
// requirement.
single: true
}
},
// This is a static Resource, so don't attempt to persist it to the database.
// This is useful for content pages that can't be interacted with.
static: true
})

// ## Resource Hooks
// Hooks can be attached to Resources in the form of both `pre` and `post`
// middlewares.
//
// In our case, every time a Faucet is retrieved from the datastore, issue a
// call to the underlying bitcoind (wherever it may be) to get the current
// balance, and then update our copy of it.
//
// There is almost certainly a more scalable way of doing this (a cache), but
// this is left as an exercise to the reader.
Faucet.post('init', function( faucet , next ) {
// TODO: debounce / singleton
// Get balance from this faucet's instance of btcClient().
// Underlying, this is an RPC call to the backing bitcoind instance.
faucet.btcClient().getBalance('*', 1, function(err, balance, resHeaders) {
if (err) console.log(err);

if (err) return console.log(err);
// Set and persist the updated balance to the storage engine.
faucet.balance = balance;
faucet.save(function(err) {
next( err , 'faucet stuff' );
Expand All @@ -53,30 +142,44 @@ Faucet.post('init', function( faucet , next ) {
});
});

// Before saving a Pour, validate that it is authorized.
// In our simple example, all Pours are allowed (without a rate-limit), but in a
// real-world example you will clearly want to implement some sort of validator.
Pour.pre('save', function( done ) {
var pour = this;
console.log('TODO: implement ratelimiter. pre-save() called.', pour );
done();
});

// perform actions when new pours are created
// ## Resource Events
// All Maki resources are also event emitters. This allows you to subscribe to
// events from any part of your application, and trigger custom behavior.
//
// In BitFaucet's case, let's initiate the Bitcoin transfer!
Pour.on('create', function( pour ) {

// `.populate` collects other resources automatically. Remember that
// "special" field? This is why it exists.
pour.populate({
path: '_faucet',
model: 'Faucet'
model: 'Faucet' // This defines the "Model" that we're expecting.
}, function(err, pour) {

// Initiate the Bitcoin transfer...
pour._faucet.btcClient().sendToAddress( pour.address , parseFloat(pour.amount) , function(err, txid) {
if (err) {
console.error(err);
// Resources in Maki expose an `update` method, which accepts a query
// and will update all matching documents.
Pour.update( pour._id , {
status: 'failed'
} , function(err) {
console.log('could not commit failure to database');
});
} else {
Pour.update( pour._id , {
status: 'broadcast'
// Let's update the `status` attribute (as previously defined).
status: 'broadcast',
txid: txid
} , function(err) {

});
Expand All @@ -85,22 +188,8 @@ Pour.on('create', function( pour ) {
});
});

maki.app.get('/', function(req, res, next) {
Faucet.query( {} , function(err, faucets) {
if (!faucets.length) {
Faucet.create({}, function(err, faucet) {
return res.render('Index', {
faucet: faucet,
index: {}
});
});
} else {
return res.render('Index', {
faucet: faucets[ 0 ],
index: {}
});
}
});
});

maki.start();
// ## Start
// `maki.start()` opens the sockets and listens for new connections for each of
// the enabled Services. By default, `http`, `https`, `ws`, and `wss` are
// available.
bitfaucet.start();
8 changes: 4 additions & 4 deletions examples/bitfaucet/views/layouts/default.jade
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ html(ng-app="maki")

script(src="/js/jquery.js")
script(src="/js/semantic.js")
script(src="/js/underscore-min.js")
script(src="/js/lodash.min.js")

script(src="/js/templates.js")
script(src="/js/highlight.pack.js")
Expand All @@ -30,11 +30,11 @@ html(ng-app="maki")
include ../partials/navbar

.contained
.ui.grid
include ../partials/flash

.ui.grid
.column.content(data-for="viewport")

include ../partials/flash

block content

footer
Expand Down
19 changes: 12 additions & 7 deletions examples/bitfaucet/views/partials/flash.jade
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
if (typeof(flash) != 'undefined')
.messages
.ui.row
if (typeof(flash.error) != 'undefined')
for error in flash.error
.alert.alert-error.alert-dismissable
button.close(type="button", data-dismiss="alert", aria-hidden="true") ×
| !{error}
.ui.error.message
i.icon.close(type="button", data-dismiss="alert")
p.header !{error}
if (typeof(flash.info) != 'undefined')
for info in flash.info
.alert.alert-info.alert-dismissable
button.close(type="button", data-dismiss="alert") ×
| !{info}
.ui.info.message
i.icon.close(type="button", data-dismiss="alert")
p.header !{info}
if (typeof(flash.success) != 'undefined')
for success in flash.success
.ui.success.message
i.icon.close(type="button", data-dismiss="alert")
p.header !{success}
Loading