Skip to content

Commit

Permalink
initial commit with most of a service written
Browse files Browse the repository at this point in the history
  • Loading branch information
grahamjenson committed Dec 22, 2014
0 parents commit 560e067
Show file tree
Hide file tree
Showing 3,576 changed files with 700,497 additions and 0 deletions.
The diff you're trying to view is too large. We only load the first 3000 changed files.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.DS_Store
78 changes: 78 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<img src="./assets/hapiger300x200.png" align="right" alt="HapiGER logo" />

Providing good recommendations can get greater user engagement and provide an opportunity to add value that would otherwise not exist. The main reason why many applications don't provide recommendations is the perceived difficulty in either implementing a custom engine or using an off-the-shelf engine.

Good Enough Recommendations (**GER**) is an attempt to reduce this difficulty by providing a recommendation engine that is scalable, easily usable and easy to integrate. HapiGER is an HTTP wrapper around GER implemented using the Hapi.js framework.

Posts about (or related to) GER:

1. Demo Movie Recommendations Site: [Yeah, Nah](http://yeahnah.maori.geek.nz/)
1. Overall description and motivation of GER: [Good Enough Recommendations with GER](http://maori.geek.nz/post/good_enough_recomendations_with_ger)
2. How GER works [GER's Anatomy: How to Generate Good Enough Recommendations](http://www.maori.geek.nz/post/how_ger_generates_recommendations_the_anatomy_of_a_recommendations_engine)
2. Testing frameworks being used to test GER: [Testing Javascript with Mocha, Chai, and Sinon](http://www.maori.geek.nz/post/introduction_to_testing_node_js_with_mocha_chai_and_sinon)
3. Bootstrap function for dumping data into GER: [Streaming directly into Postgres with Hapi.js and pg-copy-stream](http://www.maori.geek.nz/post/streaming_directly_into_postgres_with_hapi_js_and_pg_copy_stream)
4. [Postgres Upsert (Update or Insert) in GER using Knex.js](http://www.maori.geek.nz/post/postgres_upsert_update_or_insert_in_ger_using_knex_js)

#Quick Start Guide

To install hapiger

```
npm install -g hapiger
```

To start hapiger

```
hapiger
```

To create an event:

```
curl -X POST 'http://localhost:7890/default/event' -d '{person: "p1", action: "likes", thing: "x-men"}'
```

The `default` namespace is initialized on startup

To get recommendations for a user

```
curl -X GET 'http://localhost:7890/default/recommendations?person=p1&action=likes'
```

To compact the database

```
curl -X POST 'http://localhost:7890/default/compact'
```

#Namespace

Namespaces are exclusive places to store events and query for recommendations

To create a custom namespace

```
curl -X POST 'http://localhost:7890/namespace/movies'
```


Then you can add events

```
curl -X POST 'http://localhost:7890/movies/event' -d '{person: "p1", action: "likes", thing: "x-men"}'
```


A namespace also has an individual GER configuration which can be set by passing a payload, e.g.

```
curl -X POST 'http://localhost:7890/namespace' -d '{name: "movies", options: {crowd_weight: 1}}'
```

Delete a namespace (and all its events) with

```
curl -X DELETE 'http://localhost:7890/namespace/movies'
```
201 changes: 201 additions & 0 deletions assets/hapiger.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/hapiger300x200.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/hapiger70x70.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions bin/hapiger
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/env node

27 changes: 27 additions & 0 deletions config/environment.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
bb = require 'bluebird'

environment = {}
environment.logging_options = {
reporters: [{
reporter: require('good-console'),
args: [{ log: '*', response: '*' }]
}]
}
process.env.PORT=4567

console.log "ENV", process.env.NODE_ENV
switch process.env.NODE_ENV
when "test"

process.env.PORT = 3000
bb.Promise.longStackTraces()
when "production"
else
console.log "ENV", "forcing development"
bb.Promise.longStackTraces()
#AMD
if (typeof define != 'undefined' && define.amd)
define([], -> return environment)
#Node
else if (typeof module != 'undefined' && module.exports)
module.exports = environment;
120 changes: 120 additions & 0 deletions index.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#Setup the environment variables
environment = require './config/environment'

#PROMISES LIBRARY
bb = require 'bluebird'

# HAPI STACK
Hapi = require 'hapi'
Joi = require 'joi'

# GER
g = require 'ger'
knex = g.knex # postgres client
r = g.r #rethink client

#ESMs
PsqlESM = g.PsqlESM
MemESM = g.MemESM
RethinkDBESM = g.RethinkDBESM

Utils = {}

Utils.handle_error = (logger, err, reply) ->
if err.isBoom
logger.log(['error'], err)
reply(err)
else
console.log "Unhandled Error", err, err.stack
logger.log(['error'], {error: "#{err}", stack: err.stack})
reply({error: "An unexpected error occurred"}).code(500)


class HapiGER

initialize: () ->
bb.try( => @init_server())
.then( => @setup_server())
.then( => @add_server_methods())
.then( => @add_server_routes())

init_server: (esm = 'mem') ->
#SETUP SERVER
@_server = new Hapi.Server()
@_server.connection({ port: process.env.PORT });
@_esm = MemESM
@info = @_server.info
(new @_esm('default')).initialize() #add the default namespace

create_namespace: (namespace) ->
(new @_esm(namespace)).initialize()

setup_server: ->
@load_server_plugin('good', environment.logging_options)

add_server_routes: ->
@load_server_plugin('./lib/the_hapi_ger', {ESM : @_esm, ESM_OPTIONS : {}})

add_server_methods: ->


server_method: (method, args = []) ->
d = bb.defer()
@_server.methods[method].apply(@, args.concat((err, result) ->
if (err)
d.reject(err)
else
d.resolve(result)
))
d.promise


start: ->
@start_server()

stop: ->
@stop_server()



load_server_plugin: (plugin, options = {}) ->
d = bb.defer()
@_server.register({register: require(plugin), options: options}, (err) ->
if (err)
d.reject(err)
else
d.resolve()
)
d.promise

start_server: ->
d = bb.defer()
@_server.start( =>
d.resolve(@)
)
d.promise

stop_server: ->
d = bb.defer()
@_server.stop( ->
d.resolve()
)
d.promise






#AMD
if (typeof define != 'undefined' && define.amd)
define([], -> return HapiGER)
#Node
else if (typeof module != 'undefined' && module.exports)
module.exports = HapiGER;
else
#not being required
hapiger = new HapiGER()
hapiger.initialize()
.then( -> hapiger.start())
.catch((e) -> console.log "ERROR"; console.log e.stack)
16 changes: 16 additions & 0 deletions lib/namespace.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
bb = require 'bluebird'

class NS
constructor: (@name, @options = {}) ->

NS.find = (name) ->
#returns the object with GER options
options = {}
bb.try( => new NS(name, options))

#AMD
if (typeof define != 'undefined' && define.amd)
define([], -> return NS)
#Node
else if (typeof module != 'undefined' && module.exports)
module.exports = NS;
Loading

0 comments on commit 560e067

Please sign in to comment.