Full Code of senecajs/ramanujan for AI

master 69e61a90ada5 cached
119 files
123.9 KB
37.9k tokens
30 symbols
1 requests
Download .txt
Repository: senecajs/ramanujan
Branch: master
Commit: 69e61a90ada5
Files: 119
Total size: 123.9 KB

Directory structure:
gitextract_lorkntwj/

├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── api/
│   └── api-service.js
├── base/
│   └── base.js
├── docker/
│   ├── Makefile
│   ├── api/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   └── api-service.js
│   ├── base/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   └── base.js
│   ├── docker.txt
│   ├── entry-cache/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── entry-cache-logic.js
│   │   └── entry-cache-service.js
│   ├── entry-store/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── entry-store-logic.js
│   │   └── entry-store-service.js
│   ├── fanout/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── fanout-logic.js
│   │   └── fanout-service.js
│   ├── follow/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── follow-logic.js
│   │   └── follow-service.js
│   ├── front/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── front.js
│   │   └── www/
│   │       └── res/
│   │           └── site.css
│   ├── home/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── home-service.js
│   │   └── www/
│   │       ├── home.html
│   │       └── layout.html
│   ├── index/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── index-logic.js
│   │   └── index-service.js
│   ├── mine/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── mine-service.js
│   │   └── www/
│   │       ├── home.html
│   │       ├── layout.html
│   │       └── mine.html
│   ├── post/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── post-logic.js
│   │   └── post-service.js
│   ├── ramanujan.yml
│   ├── repl/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── monitor.js
│   │   └── repl-service.js
│   ├── reserve/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── reserve-logic.js
│   │   └── reserve-service.js
│   ├── search/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── search-service.js
│   │   └── www/
│   │       ├── home.html
│   │       ├── layout.html
│   │       └── search.html
│   ├── shared/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   └── package.json
│   ├── timeline/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   └── timeline-shard-service.js
│   └── timeline-shard/
│       ├── Dockerfile
│       ├── Makefile
│       ├── timeline-logic.js
│       └── timeline-service.js
├── entry-cache/
│   ├── entry-cache-logic.js
│   ├── entry-cache-service.js
│   └── test/
│       └── entry-cache-test.js
├── entry-store/
│   ├── entry-store-logic.js
│   ├── entry-store-service.js
│   └── test/
│       └── entry-store-test.js
├── fanout/
│   ├── fanout-logic.js
│   ├── fanout-service.js
│   └── test/
│       └── fanout-test.js
├── follow/
│   ├── follow-logic.js
│   ├── follow-service.js
│   └── test/
│       └── follow-test.js
├── front/
│   ├── front.js
│   └── www/
│       └── res/
│           └── site.css
├── fuge/
│   └── fuge.yml
├── home/
│   ├── home-service.js
│   └── www/
│       ├── home.html
│       └── layout.html
├── index/
│   ├── index-logic.js
│   ├── index-service.js
│   └── test/
│       └── index-test.js
├── mine/
│   ├── mine-service.js
│   └── www/
│       ├── layout.html
│       └── mine.html
├── monitor/
│   └── monitor.js
├── package.json
├── post/
│   ├── post-logic.js
│   ├── post-service.js
│   └── test/
│       └── post-test.js
├── repl/
│   └── repl-service.js
├── reserve/
│   ├── reserve-logic.js
│   ├── reserve-service.js
│   └── test/
│       └── reserve-test.js
├── search/
│   ├── search-service.js
│   └── www/
│       ├── layout.html
│       └── search.html
├── start.sh
└── timeline/
    ├── test/
    │   └── timeline-test.js
    ├── timeline-logic.js
    ├── timeline-service.js
    └── timeline-shard-service.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directory
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules

# Optional npm cache directory
.npm

# Optional REPL history
.node_repl_history

*~

node_modules*

================================================
FILE: .travis.yml
================================================
sudo: required
dist: trusty
language: node_js

node_js:
  - "6"
  - "node"

================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) Richard Rodger and other contributors 2015-2016.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# ramanujan

This project is an implementation of a microblogging system (similar
to the basic functionality of [Twitter](http://twitter.com)) using the
[microservice architecture](http://www.richardrodger.com/seneca-microservices-nodejs#.VyCjoWQrL-k)
and [Node.js](https://nodejs.org). It is the example system discussed
in Chapter 1 of [The Tao of Microservices](http://bit.ly/rmtaomicro)
book.


This purpose of this code base to help you learn how to design and
build microservice systems. You can follow the construction through
the following steps:

  * [Informal Requirements](#informal-requirements)
  * [Message specification](#message-specification)
  * [Service specification](#service-specification)

The system uses the
[Seneca microservice framework](http://senecajs.org) to provide
inter-service communication, and the
[fuge microservice development tool](https://github.com/apparatus/fuge) to manage
services on a local development machine.

The system is also a demonstration of the
[SWIM protocol](https://www.cs.cornell.edu/~asdas/research/dsn02-swim.pdf)
for peer-to-peer service discovery. A service registry is not needed
as the network automatically reconfigures as microservices are added
and removed.

## Scope of the system

The system shows implementations of some of the essential features of
a microblogging system, but not all. Of particular focus is the use of
use of separate microservices for separate content pages, the use of
messages for data manipulation, and the use of a reactive message flow
for scaling.

The system does not provide for full accounts, or user
authentication. This could be added relatively easily using the
seneca-auth and seneca-user plugins. Avoiding the need to login makes
it easier to experiment as you can check multiple user experiences in
the browser.

The system exposes a (RESTish) JSON API over HTTP. However, the user
interface does _not_ use any client-side JavaScript, and entirely
delivered by server-side templates. This is an old school POST and
redirect architecture to keep things simple and focused on the
server-side.

The system does not use persistent storage. You can easily make the
data persistent by using a Seneca data storage plugin. Keeping
everything in memory makes for faster development, easier
experimentation, and lets you reboot the system if you end up with
corrupted data during development.

This system also provides an example of message tracing, using <a
href="http://zipkin.io/">Zipkin</a>.

This example codebase does not provide a production deployment
configuration. It does however provide a Docker Swarm example that you
can start building from.


## Unit test examples

The system also includes example code for unit testing microservices.
The unit test code for each service is in the `test` subfolder of each
microservice folder.

To run all the tests, use:

``js
npm test
``

The microservices can be unit tested independently and offline. Mock
messages are used to isolate each microservice from its network
dependencies.


## Running the system

The system is implemented in Node.js. You will need to have Node.js
version 4.0 or greater installed.

You can run the system directly from the command line by running the
`start.sh` script:


```sh
$ ./start.sh
```

This starts all the microservices in the background. While this is a
quick way to get started, and verify that everything works, it is not
the most convenient option.

To have more control, you can use
[fuge](https://github.com/apparatus/fuge) to run the microservice
processes. Detailed instructions are provided next.

You can also use Docker to run the services. Example Dockerfiles are
provided in the
[docker folder](https://github.com/senecajs/ramanujan/tree/master/docker). See
below for more details.



## Running with fuge


#### Step 0: Install fuge

Follow the instructions at [fuge repository](https://github.com/apparatus/fuge).

_fuge_ is a development tool that lets you manage and control a
microservice system for local development. The ramanjun repository is
preconfigured for fuge (see the fuge folder), so you don't have to set
anything up. The ramanujan system has 14 microservices (at last
count), so you really do need a local tool to help run the system.

This is trade-off that you make when you choose the microservice
architecture. You can move faster because you have very low coupling,
and thus lower technical debt, but you will need more automation to
manage the higher number of moving parts.

#### Step 1: Clone the repository

Use git to clone the repository to a local development folder of your choice

```sh
$ git clone https://github.com/senecajs/ramanujan.git
```

#### Step 2: Download dependencies

The system needs a number of Node.js modules from npmjs.org to
function correctly. These are the only external dependencies.

```sh
$ npm install
```

Wait until the downloads complete. Some modules will require local
compilation. If you run into problems due to your operating system,
using a [Linux virtual machine](https://www.virtualbox.org/) is
probably your fastest solution. If you are using Windows,
[configuring msbuild](https://github.com/Microsoft/nodejs-guidelines/blob/master/windows-environment.md#compiling-native-addon-modules)
first is a good place to start.


The Zipkin message tracing is optional, and the system will work fine
if there is no Zipkin installation. However, it is pretty easy to set
one up using <a href="http://docker.com">Docker</a>:

```sh
$ docker run -d -p 9411:9411 openzipkin/zipkin
```

Once you've run through some of the use cases, open <a
href="http://localhost:9411/">http://localhost:9411/</a> to see the
message traces. Note that this is a demonstration system, so all
traces are captured. In production you'll want to use a much lower
sampling rate - see the Zipkin documentation for details.




#### Step 3: Run fuge

From within the repository folder, run the fuge shell.

```sh
$ fuge shell fuge/fuge.yml
```

This will start fuge, output some logging messages about the ramanujan services, and then place you in an interactive repl:

```sh
...
starting shell..
? fuge>
```

Enter the command `help` to see a list of commands. Useful commands
are `ps` to list the status of the services (try it!), and `exit` to
shutdown all services and exit. If your system state becomes corrupted
in some way (this often happens during development due to bugs in
microservices), exit fuge completely and restart the fuge shell.


#### Step 4: Start up the system

To start the system, use the fuge command:

```sh
...
? fuge> start all
```

You see a list of startup logs from each service. _fuge_ prefixes the
logs for each service with the service names, and gives them different
colors so they are easy to tell apart. This also makes is easy to
review message flows. The system takes about a few seconds to start
all microservices.

Now use the `ps` command to list the state of the services. They
should all be running.

### Using the system

Open your web browser to interact with the system. The steps below
define a "happy path" to validate the basic functionality of the
system.

#### Step 1: Post microblogs entries for user _foo_

Open `http://localhost:8000/foo`.

This is the homepage for the user _foo_, and shows their timeline. The
timeline is a list of recent microblog entries from all users that the
user _foo_ follows, and also entries from _foo_ themselves.


At first there are no entries, so go ahead and post an entry, say:

> _three colors: blue_

<img src="https://github.com/senecajs/ramanujan/blob/master/img/rm01.png" width="440">

Click the _post_ button or hit return. You should see the new entry.

<img src="https://github.com/senecajs/ramanujan/blob/master/img/rm02.png" width="440">

Post another entry, say:

> _three colors: white_

You should see both entries listed, with the most recent one at the
top. This is the timeline for user _foo_.


#### Step 2: Review microblogs for user _foo_

Open `http://localhost:8000/mine/foo` (Or click on the _Mine_ navigation tab).

This shows only the entries for user _foo_, omitting entries for followers.

You can use this page to verify the entry list for a given user.

<img src="https://github.com/senecajs/ramanujan/blob/master/img/rm03.png" width="440">


#### Step 3: Load search page of user _bar_, and follow user _foo_

Open `http://localhost:8000/search/bar`.

You are now acting as user _bar_. Use the text _blue_ as a search query:

Click on the _follow_ button. Now user _bar_ is following user _foo_.

<img src="https://github.com/senecajs/ramanujan/blob/master/img/rm04.png" width="440">


#### Step 4: Review timeline for user _bar_

Open `http://localhost:8000/bar` (Or click on the _Home_ navigation tab).

You should see the entries from user _foo_, as user _bar_ is now a follower.

<img src="https://github.com/senecajs/ramanujan/blob/master/img/rm05.png" width="440">


#### Step 5: Post microblog entries for user _bar_

Enter and post the text:

> _the sound of music_

The timeline for user _bar_ now includes entries from both users _foo_
and _bar_.

<img src="https://github.com/senecajs/ramanujan/blob/master/img/rm06.png" width="440">


#### Step 5: Post microblog entries for user _foo_

Return to user _foo_. Open `http://localhost:8000/foo`.

Post a new entry:

> _three colors: red_

You should see entries only for user _foo_, as _foo_ does **not** follow _bar_.

<img src="https://github.com/senecajs/ramanujan/blob/master/img/rm07.png" width="440">


#### Step 6: Load microblog timeline of user _bar_

Go back to user _bar_. Open `http://localhost:8000/bar`.

You should see an updated list of entries, included all the entries
for user _foo_, as _bar_ **does** follow _foo_.

<img src="https://github.com/senecajs/ramanujan/blob/master/img/rm08.png" width="440">


### Starting and stopping services

One of the main benefits of a microservice system is that you can
deploy services independently. In a local development setting this
means you should be able to start and stop services independently,
without stopping and starting the entire system. This has a huge
productivity benefit as you don't have to wait for the entire system
to ready itself.

To work on a particular service, update the code for that service, and
then stop and restart the service to see the new functionality. The
rest of the system keeps working. To really get the maximum benefit
from this technique, you need to avoid the use of schema validation
for your messages, and you must avoid creating hard couplings
(services should not know about each other). That is why the Seneca
framework provides pattern matching and transport independence as key
features - they enable rapid development.

The payoff for more deployment complexity is that you can change parts
of the system dynamically - don't lose that ability!

_fuge_ allows you to start and stop services using the 'start' and
'stop' commands.

To stop a service (say, _search_), use the command:

```sh
? fuge> stop search
```

If you now try to use the search feature, it will fail, but other
pages will still work. Another important benefit of microservices is that they can isolate errors in this way.

To restart the _search_ service, use:

```sh
? fuge> start search
```

And the search functionality works again. Notice that you did not have
to do any manual configuration to let the other services know about
the new instance of the _search_ service. Notice also that the other
services knew almost instantaneously about the the new instance of the
_search_ service. That's becuase the SWIM algorithm propogated that
information quickly and efficiently throughout the network. No need
for 30 second timeouts to detect errors - SWIM works much more quickly
as it has many observers (the other services) so can detect failure,
and new services, very quickly with a high degree of confidence.

You can also run multiple instances of the same service. This lets you
scale to handle load. The underlying seneca-mesh network will
automatically round-robin messages between all available services for
a given message. Just start the service again:

```sh
? fuge> start search
```

And is you now run the `ps` command in fuge, you'll see the count is 2
instances.


### Accessing the network REPL

The system comes with a REPL service that lets you submit messages to the network manually. This is very useful for debugging. Access the REPL by telnetting into it:

```sh
$ telnet localhost 10001
```

Use the following message to see the user _foo's_ timeline:

```sh
seneca 2.0.1 7k/repl> timeline:list,user:foo
IN  000000: { timeline: 'list', user: 'foo' } # t7/39 timeline:* (6ln6zlc2qaer) transport_client
OUT 000000: { '0':
   { user: 'foo',
     text: 'three colors: red',
     when: 1461759716373,
     can_follow: false },
  '1':
   { user: 'foo',
     text: 'three colors: white',
     when: 1461759467135,
     can_follow: false },
  '2':
   { user: 'foo',
     text: 'three colors: blue',
     when: 1461759353996,
     can_follow: false } }
```

You can enter messages directly into the terminal, in JSON format (the
format is lenient, see
[jsonic](https://github.com/rjrodger/jsonic)). The output will show
the message data `IN` and `OUT` of the network.

The REPL is a JavaScript console environment. There is a `seneca`
object that you can use directly, calling any methods of the seneca
API.

```sh
seneca 2.0.1 7k/repl> seneca.id
'7k/repl'
```

To get a list of all services on the network, and which messages they
listen for, try:

```sh
seneca 2.0.1 7k/repl> role:mesh,get:members
IN  000001: { role: 'mesh', get: 'members' } # aa/ie get:members,role:mesh (9mxp6qx6zyox) get_members
OUT 000001: {
  ...
  '4':
   { pin: 'timeline:*',
     port: 54932,
     host: '0.0.0.0',
     type: 'web',
     model: 'consume',
     instance: 'gt/timeline-shard' },
  ...
  }
```

This message is so useful, that the repl service defines an alias for it: `m`.

The default configuration of the system uses shortened identifers to
make debugging easier.


### Using the monitor

You can monitor the state of each service, and the message patterns
that it responds to, by running the `monitor` service separately in
it's own terminal window. The `monitor` service prints a table of
showing each service, and dynamically updates the table as services
come and go. See
[seneca-mesh](https://github.com/senecajs/seneca-mesh) for details.

```sh
$ node monitor/monitor.js 
```


## Using Docker

You'll need to have the latest version of
[Docker](https://www.docker.com/) installed.

The [docker](https://github.com/senecajs/ramanujan/tree/master/docker)
folder contains Docker image setup Makefiles and Dockerfiles. Run the
top level `Makefile` to build all the images:

```
$ cd docker
$ make
```

Then deploy all the images using Docker Stack:

```
$ docker stack deploy -c ramanujan.yml ramanujan
```

This will start up everything. The containers run in their own overlay
network, but you will be able to access the website and repl on
localhost as with fuge.

If things go funny (hey, it's Docker), delete the stack, restart
Docker, and try again:

```
$ docker stack rm ramanujan
```

You can see some information about the containers with these commands:

```
$ docker stats
$ docker services ls
$ docker ps
```

To view the monitor, run the it on the `repl` container:

```
$ docker exec -it `docker ps | grep repl | cut -f 1 -d ' '` /bin/sh
# node monitor.js
```


## Informal Requirements

> TODO

## Message Specification

> TODO

## Service Specification

> TODO

## Help and Questions

[github issue]: https://github.com/senecajs/ramanujan/issues
[gitter-url]: https://gitter.im/senecajs/ramanujan


## License
Copyright (c) Richard Rodger and other contributors 2015-2016, Licensed under [MIT](/LICENSE).


================================================
FILE: api/api-service.js
================================================
"use strict"

var PORT = process.env.PORT || process.argv[2] || 0
var HOST = process.env.HOST || process.argv[3] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[4] || '').split(',')
var SILENT = process.env.SILENT || process.argv[5] || 'true'


var Hapi   = require('hapi')
var Chairo = require('chairo')
var Seneca = require('seneca')
var Rif    = require('rif')


var tag = 'api'

var server = new Hapi.Server()
var rif = Rif()


var host = rif(HOST) || HOST


server.connection({
    port: PORT,
    host: host
})


server.register({
  register: Chairo,
  options:{
    seneca: Seneca({
      tag: tag,
      internal: {logger: require('seneca-demo-logger')},
      debug: {short_logs:true}
    })
    //.use('zipkin-tracer', {sampling:1})
  }
})


server.register({
  register: require('wo'),
  options:{
    bases: BASES,
    route: [
        {path: '/api/ping'},
        {path: '/api/post/{user}', method: 'post'},
        {path: '/api/follow/{user}', method: 'post'},
    ],
    sneeze: {
      host: host,
      silent: JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  }
})


server.route({
  method: 'GET', path: '/api/ping',
  handler: function( req, reply ){
    server.seneca.act(
      'role:api,cmd:ping',
      function(err,out) {
        reply(err||out)
      }
  )}
})

server.route({
  method: 'POST', path: '/api/post/{user}',
  handler: function( req, reply ){

      console.log('/api/post A', req.params, req.payload)
      
    server.seneca.act(
      'post:entry',
      {user:req.params.user, text:req.payload.text},
      function(err,out) {
	  console.log('/api/post B', err, out)

	  if( err ) return reply.redirect('/error')

        reply.redirect(req.payload.from)
      }
    )}
})

server.route({
  method: 'POST', path: '/api/follow/{user}',
  handler: function( req, reply ){
    server.seneca.act(
      'follow:user',
      {user:req.params.user, target:req.payload.user},
      function(err,out) {
        if( err ) return reply.redirect('/error')

        reply.redirect(req.payload.from)
      }
    )}
})


server.seneca
  .add('role:api,cmd:ping', function(msg,done){
    done( null, {pong:true,api:true,time:Date.now()})
  })
    .use('mesh',{
	host: host,
	bases: BASES,
	sneeze: {
          silent: JSON.parse(SILENT),
          swim: {interval: 1111}
        }
    })


server.start(function(){
  console.log(tag,server.info.host,server.info.port)
})



================================================
FILE: base/base.js
================================================
// node base.js base0 39000 127.0.0.1 127.0.0.1:39000,127.0.0.1:39001
// node base.js base1 39001 127.0.0.1 127.0.0.1:39000,127.0.0.1:39001

var TAG = process.env.TAG || process.argv[2] || 'base'
var PORT = process.env.PORT || process.argv[3] || 39999
var HOST = process.env.HOST || process.argv[4] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[5] || '').split(',')
var SILENT = process.env.SILENT || process.argv[6] || 'true'


require('seneca')({
  tag: TAG,
  internal: {logger: require('seneca-demo-logger')},
  debug: {short_logs:true}
})
  //.test(console.log,'print')
  //.use('zipkin-tracer', {sampling:1})
  .use('mesh',{
    isbase: true,
    port: PORT,
    host: HOST,
    bases: BASES,
    pin:'role:mesh',
    sneeze: {
      silent: JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  })
  .ready(function(){
    console.log(this.id)
  })


================================================
FILE: docker/Makefile
================================================
containers :
	$(MAKE) -C shared container
	$(MAKE) -C api container
	$(MAKE) -C base container
	$(MAKE) -C entry-cache container
	$(MAKE) -C entry-store container
	$(MAKE) -C fanout container
	$(MAKE) -C follow container
	$(MAKE) -C front container
	$(MAKE) -C home container
	$(MAKE) -C index container
	$(MAKE) -C mine container
	$(MAKE) -C post container
	$(MAKE) -C repl container
	$(MAKE) -C reserve container
	$(MAKE) -C search container
	$(MAKE) -C timeline container
	$(MAKE) -C timeline-shard container
	$(MAKE) -C monitor container

.PHONY : containers


================================================
FILE: docker/api/Dockerfile
================================================
FROM shared

ADD api-service.js .

CMD ["node", "api-service.js"]



================================================
FILE: docker/api/Makefile
================================================
container :
	cp ../../api/api-service.js .
	docker build -t api .
	docker images | grep api

run-single :
	docker service create --replicas 1 --network ramanujan  --name api -e HOST=eth0 -e BASES=base0:39000,base1:39000 api

rm-single :
	docker service rm api

clean :
	rm -f *~
	rm -f *.js
	rm -f *.json

.PHONY : container clean


================================================
FILE: docker/api/api-service.js
================================================
"use strict"

var PORT = process.env.PORT || process.argv[2] || 0
var HOST = process.env.HOST || process.argv[3] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[4] || '').split(',')
var SILENT = process.env.SILENT || process.argv[5] || 'true'


var Hapi   = require('hapi')
var Chairo = require('chairo')
var Seneca = require('seneca')
var Rif    = require('rif')


var tag = 'api'

var server = new Hapi.Server()
var rif = Rif()


var host = rif(HOST) || HOST


server.connection({
    port: PORT,
    host: host
})


server.register({
  register: Chairo,
  options:{
    seneca: Seneca({
      tag: tag,
      internal: {logger: require('seneca-demo-logger')},
      debug: {short_logs:true}
    })
    //.use('zipkin-tracer', {sampling:1})
  }
})


server.register({
  register: require('wo'),
  options:{
    bases: BASES,
    route: [
        {path: '/api/ping'},
        {path: '/api/post/{user}', method: 'post'},
        {path: '/api/follow/{user}', method: 'post'},
    ],
    sneeze: {
      host: host,
      silent: JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  }
})


server.route({
  method: 'GET', path: '/api/ping',
  handler: function( req, reply ){
    server.seneca.act(
      'role:api,cmd:ping',
      function(err,out) {
        reply(err||out)
      }
  )}
})

server.route({
  method: 'POST', path: '/api/post/{user}',
  handler: function( req, reply ){

      console.log('/api/post A', req.params, req.payload)
      
    server.seneca.act(
      'post:entry',
      {user:req.params.user, text:req.payload.text},
      function(err,out) {
	  console.log('/api/post B', err, out)

	  if( err ) return reply.redirect('/error')

        reply.redirect(req.payload.from)
      }
    )}
})

server.route({
  method: 'POST', path: '/api/follow/{user}',
  handler: function( req, reply ){
    server.seneca.act(
      'follow:user',
      {user:req.params.user, target:req.payload.user},
      function(err,out) {
        if( err ) return reply.redirect('/error')

        reply.redirect(req.payload.from)
      }
    )}
})


server.seneca
  .add('role:api,cmd:ping', function(msg,done){
    done( null, {pong:true,api:true,time:Date.now()})
  })
    .use('mesh',{
	host: host,
	bases: BASES,
	sneeze: {
          silent: JSON.parse(SILENT),
          swim: {interval: 1111}
        }
    })


server.start(function(){
  console.log(tag,server.info.host,server.info.port)
})



================================================
FILE: docker/base/Dockerfile
================================================
FROM shared

ADD base.js .

CMD ["node", "base.js"]



================================================
FILE: docker/base/Makefile
================================================
container :
	cp ../../base/base.js .
	docker build -t base .
	docker images | grep base

run-single-base0:
	docker service create --replicas 1 --network ramanujan --name base0 -e TAG=base0 -e PORT=39000 -e HOST=base0 -e BASES=base0:39000,base1:39000 base

run-single-base1:
	docker service create --replicas 1 --network ramanujan --name base1 -e TAG=base1 -e PORT=39000 -e HOST=base1 -e BASES=base0:39000,base1:39000 base

rm-single-base0:
	docker service rm base0

rm-single-base1:
	docker service rm base1

clean :
	rm -f *~
	rm -f *.js
	rm -f *.json

.PHONY : container clean


================================================
FILE: docker/base/base.js
================================================
// node base.js base0 39000 127.0.0.1 127.0.0.1:39000,127.0.0.1:39001
// node base.js base1 39001 127.0.0.1 127.0.0.1:39000,127.0.0.1:39001

var TAG = process.env.TAG || process.argv[2] || 'base'
var PORT = process.env.PORT || process.argv[3] || 39999
var HOST = process.env.HOST || process.argv[4] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[5] || '').split(',')
var SILENT = process.env.SILENT || process.argv[6] || 'true'


require('seneca')({
  tag: TAG,
  internal: {logger: require('seneca-demo-logger')},
  debug: {short_logs:true}
})
  //.test(console.log,'print')
  //.use('zipkin-tracer', {sampling:1})
  .use('mesh',{
    isbase: true,
    port: PORT,
    host: HOST,
    bases: BASES,
    pin:'role:mesh',
    sneeze: {
      silent: JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  })
  .ready(function(){
    console.log(this.id)
  })


================================================
FILE: docker/docker.txt
================================================

# These are development notes NOT instructions!


# on host 


docker-machine ls

docker-machine create --driver virtualbox manager1
docker-machine create --driver virtualbox worker1
docker-machine create --driver virtualbox worker2

docker-machine ls

# MANAGER-IP
docker-machine ip manager1

docker-machine ssh manager1

# inside manager1

docker swarm init --advertise-addr <MANAGER-IP>

docker swarm join-token worker

exit



docker-machine ssh worker1

# inside worker1

docker swarm join --token <TOKEN> <MANAGER-IP>:2377

exit



docker-machine ssh worker2

# inside worker2

docker swarm join --token <TOKEN> <MANAGER-IP>:2377

exit



docker-machine ssh manager1 -A

# inside manager1

docker node ls

docker network create --driver overlay ramanujan

docker network ls


# install emacs
tce
# emacs.tcz
# libXrandr.tcz
# make.tcz

TERM=vt100 emacs -nw  


# IMPORTANT: use the same host name everywhere otherwise swim-js mappings will fail


# TODO: order properly

docker service create --replicas 1 --network ramanujan --name base1 -e TAG=base1 -e PORT=39000 -e HOST=base
1 -e BASES=base0:39000,base1:39000 base


docker service create --replicas 1 --network ramanujan -p 10001:10001 --name repl -e TAG=repl -e REPL_HOST=0.0.0.0  -e HOST=@eth2 -e BASES=base0:3900
0,base1:39000 repl


# eth2 as publish
docker service create --replicas 1 --network ramanujan --publish 8000:8000 --name front -e HOST=eth2
 -e BASES=base0:39000,base1:39000 front


# eth0 as no publish
docker service create --replicas 1 --network ramanujan  --name home -e HOST=eth0 -e BASES=base0:39000,ba
se1:39000 home



# stack

docker stack deploy -c ramanujan.yml ramanujan
docker stack rm ramanujan


# monitor

docker exec -it `docker ps | grep repl | cut -f 1 -d ' '` /bin/sh
$ node monitor.js



================================================
FILE: docker/entry-cache/Dockerfile
================================================
FROM shared

ADD entry-cache-logic.js .
ADD entry-cache-service.js .

CMD ["node", "entry-cache-service.js"]



================================================
FILE: docker/entry-cache/Makefile
================================================
container :
	cp ../../entry-cache/entry-cache-*.js .
	docker build -t entry-cache .
	docker images | grep entry-cache

run-single :
	docker service create --replicas 1 --network ramanujan --name entry-cache -e HOST=@eth0 -e BASES=base0:39000,base1:39000 entry-cache

rm-single :
	docker service rm entry-cache


clean :
	rm -f *~
	rm -f *.js
	rm -f *.json

.PHONY : container clean


================================================
FILE: docker/entry-cache/entry-cache-logic.js
================================================
'use strict'

var _ = require('lodash')

module.exports = function entry_cache (options) {
  var seneca = this

  var cache = {}

  seneca.add('store:save,kind:entry', function(msg, done) {
    delete cache[msg.user]
    msg.cache = true
    this.act(msg, done)
  })


  seneca.add('store:list,kind:entry', function(msg, done) {
    if( cache[msg.user] ) {
      return done( null, cache[msg.user] )
    }

    msg.cache = true

    this.act(msg, function(err,list){
      if(err) return done(err)
      cache[msg.user] = list
      done(null,list)
    })
  })
}


================================================
FILE: docker/entry-cache/entry-cache-service.js
================================================
var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[3] || '').split(',')
var SILENT = process.env.SILENT || process.argv[4] || 'true'

require('seneca')({
  tag:'entry-cache',
  internal: {logger: require('seneca-demo-logger')},
  debug: {short_logs:true}
})
    //.use('zipkin-tracer', {sampling:1})
  .use('basic')
  .use('entity')
  .use('entry-cache-logic')
  .use('mesh',{
    pin: 'store:*,kind:entry',
    bases: BASES,
    host: HOST,
    sneeze: {
      silent: JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  })
  .ready(function(){
    console.log(this.id)
  })


================================================
FILE: docker/entry-store/Dockerfile
================================================
FROM shared

ADD entry-store-logic.js .
ADD entry-store-service.js .

CMD ["node", "entry-store-service.js"]



================================================
FILE: docker/entry-store/Makefile
================================================
container :
	cp ../../entry-store/entry-store-*.js .
	docker build -t entry-store .
	docker images | grep entry-store

run-single :
	docker service create --replicas 1 --network ramanujan --name entry-store -e HOST=@eth0 -e BASES=base0:39000,base1:39000 entry-store

rm-single :
	docker service rm entry-store


clean :
	rm -f *~
	rm -f *.js
	rm -f *.json

.PHONY : container clean


================================================
FILE: docker/entry-store/entry-store-logic.js
================================================
module.exports = function entry_store (options) {
  var seneca = this

  seneca.add('store:save,kind:entry', function(msg, done) {
    this
      .make('entry', {
        when: msg.when,
        user: msg.user,
        text: msg.text
      })
      .save$(function(err, entry) {
        if(err) return done(err)

        this.act(
          {
            timeline: 'insert',
            users: [msg.user],
          }, 
          entry, 
          function(err) {
            return done(err, entry)
          })
      })
  })

  seneca.add('store:list,kind:entry', function(msg, done) {
    this
      .make('entry')
      .list$( {user: msg.user}, function(err, list) {
        if(err) return done(err)

        list.reverse( function(a, b) {
          return a.when - b.when
        })

        done( null, list )
      })
  })
}


================================================
FILE: docker/entry-store/entry-store-service.js
================================================
var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[3] || '').split(',')
var SILENT = process.env.SILENT || process.argv[4] || 'true'

require('seneca')({
  tag: 'entry-store',
  internal: {logger: require('seneca-demo-logger')},
  debug: {short_logs: true}
})
    //.use('zipkin-tracer', {sampling:1})
  .use('basic')
  .use('entity')
  .use('entry-store-logic')
  .use('mesh',{
    pin: 'store:*,kind:entry,cache:true',
    bases: BASES,
    host: HOST,
    sneeze:{
      silent: JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  })
  .ready(function(){
    console.log(this.id)
  })


================================================
FILE: docker/fanout/Dockerfile
================================================
FROM shared

ADD fanout-logic.js .
ADD fanout-service.js .

CMD ["node", "fanout-service.js"]



================================================
FILE: docker/fanout/Makefile
================================================
container :
	cp ../../fanout/fanout-*.js .
	docker build -t fanout .
	docker images | grep fanout

run-single :
	docker service create --replicas 1 --network ramanujan --name fanout -e HOST=@eth0 -e BASES=base0:39000,base1:39000 fanout

rm-single :
	docker service rm fanout


clean :
	rm -f *~
	rm -f *.js
	rm -f *.json

.PHONY : container clean


================================================
FILE: docker/fanout/fanout-logic.js
================================================
'use strict'

var _ = require('lodash')

module.exports = function fanout (options) {
  var seneca = this

  seneca.add('fanout:entry', function(msg, done) {
    done()

    var entry = this.util.clean(msg)
    delete entry.fanout

    this.act('follow:list,kind:followers',{user:entry.user},function(err,userlist){
      if(err) return

      if( userlist && 0 < userlist.length ) {
        this.act({
          timeline: 'insert',
          users: userlist,
        }, entry)
      }
    })
  })
}


================================================
FILE: docker/fanout/fanout-service.js
================================================
var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[3] || '').split(',')
var SILENT = process.env.SILENT || process.argv[4] || 'true'

require('seneca')({
  tag: 'fanout',
  internal: {logger: require('seneca-demo-logger')},
  debug: {short_logs: true}
})
    //.use('zipkin-tracer', {sampling:1})
  .use('fanout-logic')

  .add('info:entry', function(msg,done){
    delete msg.info
    this.act('fanout:entry',msg,done)
  })

  .use('mesh',{
    listen:[
      {pin: 'fanout:*'},
      {pin: 'info:entry', model:'observe'}
    ],
    bases: BASES,
    host: HOST,
    sneeze: {
      silent: JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  })

  .ready(function(){
    console.log(this.id)
  })


================================================
FILE: docker/follow/Dockerfile
================================================
FROM shared

ADD follow-logic.js .
ADD follow-service.js .

CMD ["node", "follow-service.js"]



================================================
FILE: docker/follow/Makefile
================================================
container :
	cp ../../follow/follow-*.js .
	docker build -t follow .
	docker images | grep follow

run-single :
	docker service create --replicas 1 --network ramanujan --name follow -e HOST=@eth0 -e BASES=base0:39000,base1:39000 follow

rm-single :
	docker service rm follow


clean :
	rm -f *~
	rm -f *.js
	rm -f *.json

.PHONY : container clean


================================================
FILE: docker/follow/follow-logic.js
================================================
var _ = require('lodash')

module.exports = function follow (options) {
  var seneca = this

  seneca.add('follow:user', function(msg, done) {
    var seneca = this

    relate( seneca, 'followers', msg.target, msg.user, true, function(err) {
      if( err ) return done(err)

      relate( seneca, 'following', msg.user, msg.target, true, function(err) {
        if( err ) return done(err)

        seneca.act('store:list,kind:entry',{user:msg.target}, function(err,list) {
          if( err ) return done(err)

          _.each(list,function(entry){
            seneca.act({
              timeline: 'insert',
              users: [msg.user],
            }, entry.data$())
          })

          done()
        })
      })
    })
  })


  seneca.add('follow:list', function(msg,done){
    this
      .make('follow')
      .load$(msg.user, function(err,follow){
        var list = (follow && follow[msg.kind]) || []
        done(err, list)
      })
  })


  function relate(seneca,relation,from,to,create,done) {
    seneca
      .make('follow')
      .load$(from, function(err, follow) {
        if( err ) return done(err)
        
        if (follow) {
          add_follower( null, follow, done )
        }
        else if (create) {
          this.act('reserve:create', {key: 'follow/'+from}, function (err, status) {
            if( err ) return done(err)
            
            if( !status.ok ) {
              return relate(this,relation,from,to,false,done)
            }

            var follow = this.make('follow',{id$:from})
            follow[relation] = []
            add_follower(err, follow, function (err) {
              if( err ) return done(err)
              
              this.act('reserve:remove', {key: 'follow/'+from})
              done()
            })
          })
        }

        function add_follower( err, follow, done ) {
          if( err ) return done(err)

          follow[relation] = (follow[relation] || [])
          follow[relation].push(to)
          follow[relation] = _.uniq(follow[relation])

          follow.save$(done)
        }
      })
  }
}
 


================================================
FILE: docker/follow/follow-service.js
================================================
var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[3] || '').split(',')
var SILENT = process.env.SILENT || process.argv[4] || 'true'


require('seneca')({
  tag: 'follow',
  internal: {logger: require('seneca-demo-logger')},
  debug: {short_logs: true}
})
    //.use('zipkin-tracer', {sampling:1})
  .use('entity')
  .use('follow-logic')
  .use('mesh',{
    pin: 'follow:*',
    bases: BASES,
    host: HOST,
    sneeze: {
      silent: JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  })

  .ready(function(){
    console.log(this.id)
  })


================================================
FILE: docker/front/Dockerfile
================================================
FROM shared

ADD front.js .
ADD www www

CMD ["node", "front.js"]



================================================
FILE: docker/front/Makefile
================================================
container :
	cp ../../front/front.js .
	cp -r ../../front/www .
	docker build -t front .
	docker images | grep front

run-single :
	docker service create --replicas 1 --network ramanujan --publish 8000:8000 --name front -e HOST=eth2 -e BASES=base0:39000,base1:39000 front

rm-single :
	docker service rm front

clean :
	rm -f *~
	rm -f *.js
	rm -f *.json

.PHONY : container clean


================================================
FILE: docker/front/front.js
================================================
"use strict"
var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[3] || '').split(',')
var SILENT = process.env.SILENT || process.argv[4] || 'true'


var Hapi = require('hapi')
var Rif = require('rif')

var server = new Hapi.Server()
var rif = Rif()


var host = rif(HOST) || HOST


server.connection({ 
  port: 8000 // test with http://localhost:8000/api/ping
})

server.register(require('inert'))

server.register({
  register: require('wo'),
  options: {
    bases: BASES,
      sneeze: {
	host: host,
	silent: JSON.parse(SILENT),
        swim: {interval: 1111}
      }
  }
})

server.route({ 
  method: 'GET', path: '/api/ping', 
  handler: {
    wo: {}
  }
})

server.route({
  method: 'POST', path: '/api/post/{user}', 
  handler: {
    wo: {
      passThrough: true
    }
  }
})

server.route({
  method: 'POST', path: '/api/follow/{user}', 
  handler: {
    wo: {
      passThrough: true
    }
  }
})


server.route({ 
  method: 'GET', path: '/mine/{user}', 
  handler: {
    wo: {}
  }
})


server.route({ 
  method: ['GET','POST'], path: '/search/{user}', 
  handler: {
    wo: {}
  }
})


server.route({ 
  method: 'GET', path: '/{user}', 
  handler: {
    wo: {}
  }
})

server.route({
  path: '/favicon.ico',
  method: 'get',
  config: {
    cache: {
      expiresIn: 1000*60*60*24*21
    }
  },
  handler: function(request, reply) {
    reply().code(200).type('image/x-icon')
  }
})

server.route({
  method: 'GET',
  path: '/res/{path*}',
  handler: {
    directory: {
      path: __dirname + '/www/res',
    }
  }
})


server.start(function(){
  console.log('front',server.info.uri)
})


================================================
FILE: docker/front/www/res/site.css
================================================
* {
  font-family: arial;
  padding: 0px;
  margin: 0px;
  text-decoration: none;
}

body {
  background-color: #eef;
}

input {
  border: 1px solid #222;
  font-size: 14pt;
  padding: 2px;
}

input[type='text'] {
  width: 25%;
}

div.header {
  background-color: #222;
  margin: 0px 0px 10px 0px;
  padding: 4px;
}

div.header a {
  display: inline-block;
  padding: 4px;
  color: #eee;
}

div.header .nav_active {
  background-color: #eef;
  color: #222;
}

div.container {
  padding: 4px;
}

div.entry {
  padding: 2px;
  margin: 4px 2px;
  border: 1px solid #ccc;
  width: 33%;
  color: #666;
}

div.text {
  color: #222;
  padding: 2px;
  margin-top: 2px;
}


================================================
FILE: docker/home/Dockerfile
================================================
FROM shared

ADD home-service.js .
ADD www www

CMD ["node", "home-service.js"]



================================================
FILE: docker/home/Makefile
================================================
container :
	cp ../../home/home-service.js .
	cp -r ../../home/www .
	docker build -t home .
	docker images | grep home

run-single :
	docker service create --replicas 1 --network ramanujan  --name home -e HOST=eth0 -e BASES=base0:39000,base1:39000 home

rm-single :
	docker service rm home

clean :
	rm -f *~
	rm -f *.js
	rm -f *.json

.PHONY : container clean


================================================
FILE: docker/home/home-service.js
================================================
"use strict"

var PORT = process.env.PORT || process.argv[2] || 0
var HOST = process.env.HOST || process.argv[3] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[4] || '').split(',')
var SILENT = process.env.SILENT || process.argv[5] || 'true'


var hapi       = require('hapi')
var chairo     = require('chairo')
var vision     = require('vision')
var inert      = require('inert')
var handlebars = require('handlebars')
var _          = require('lodash')
var moment     = require('moment')
var Seneca     = require('seneca')
var Rif        = require('rif')


var tag = 'home'

var server = new hapi.Server()
var rif = Rif()


var host = rif(HOST) || HOST


server.connection({
    port: PORT,
    host: host
})


server.register( vision )
server.register( inert )

server.register({
  register:chairo,
  options:{
    seneca: Seneca({
      tag: tag,
      internal: {logger: require('seneca-demo-logger')},
      debug: {short_logs:true}
    })
      //.use('zipkin-tracer', {sampling:1})
  }
})

server.register({
  register: require('wo'),
  options:{
    bases: BASES,
    route: [
        {path: '/{user}'},
    ],
    sneeze: {
      host: host,
      silent: JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  }
})


server.views({
  engines: { html: handlebars },
  path: __dirname + '/www',
  layout: true
})


server.route({
  method: 'GET', path: '/{user}',
  handler: function( req, reply )
  {
    server.seneca.act(
      'timeline:list',
      {user:req.params.user},
      function( err, entrylist ) {
        if(err) {
          entrylist = []
        }

        reply.view('home',{
          user: req.params.user,
          entrylist: _.map(entrylist,function(entry){
            entry.when = moment(entry.when).fromNow()
            return entry
          })
        })
      })
  }
})


server.seneca.use('mesh',{
    host:host,
    bases:BASES,
    sneeze: {
      silent: JSON.parse(SILENT),
      swim: {interval: 1111}
    }
})


server.start(function(){
  console.log(tag,server.info.host,server.info.port)
})



================================================
FILE: docker/home/www/home.html
================================================
<form action="/api/post/{{user}}" method="post">
<input type="text" name="text">
<input type="hidden" name="from" value="/{{user}}">
<input type="submit" value="post">
</form>

<br>

{{#each entrylist}}
<div class="entry">
<form action="/api/follow/{{../user}}" method="post">
<b><a href="/{{this.user}}">@{{this.user}}</a></b> <small>{{this.when}}</small>
<input type="hidden" name="from" value="/{{../user}}">
<input type="hidden" name="user" value="{{this.user}}">
{{#if can_follow}}
<input type="submit" value="follow">
{{/if}}
<br>
<div class="text">
{{this.text}}
</div>
</form>
</div>
{{/each}}




================================================
FILE: docker/home/www/layout.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Microblog</title>
  <link rel="stylesheet" href="/res/site.css">
</head>
<body>

  <div class="header">
    <a href="/{{user}}" class="nav_active">Home</a>
    <a href="/mine/{{user}}">Mine</a> 
    <a href="/search/{{user}}">Search</a>
  </div>

  <div class="container">
    {{{ content }}}
  </div>

</body>
</html>



================================================
FILE: docker/index/Dockerfile
================================================
FROM shared

ADD index-logic.js .
ADD index-service.js .

CMD ["node", "index-service.js"]



================================================
FILE: docker/index/Makefile
================================================
container :
	cp ../../index/index-*.js .
	docker build -t index .
	docker images | grep index

run-single :
	docker service create --replicas 1 --network ramanujan --name index -e HOST=@eth0 -e BASES=base0:39000,base1:39000 index

rm-single :
	docker service rm index


clean :
	rm -f *~
	rm -f *.js
	rm -f *.json

.PHONY : container clean


================================================
FILE: docker/index/index-logic.js
================================================
// Business logic for the index microservice.
// Provides a full text search index for microblog entries.


// Modules providing a simple in-memory full text search index.
// In production you could replace these with API calls to elasticsearch
// or similar.
var Levelup = require('levelup')
var Memdown = require('memdown')
var Search  = require('search-index')


// This is the standard way to define a Seneca plugin.
module.exports = function index (options) {

  // The plugin Seneca instance is provided by `this`.
  // This Seneca instance tracks patterns against this plugin
  // as an aid to debugging.
  var seneca = this


  // The search index. This is the internal state of the service. In general.
  // services should *not* have internal state, as it has to be synchronized
  // between multiple instances. This service is purely for demonstration purposes,
  // and only a single instance should be run.
  var index


  // The Seneca patterns that this plugin defines.
  // This is the `interface` for this plugin - matching messages will end up here.
  seneca.add('search:query', search_query)
  seneca.add('search:insert', search_insert)
  seneca.add('init:index', init)


  // Query the search index.
  // The implementation logic consists of calls to the search index API.
  function search_query (msg, done) {
    console.log(terms)

    var terms = msg.query.split(/ +/)

    var query = {
      query: {
        AND: {text:terms}
      }
    }

    index.search(query, function (err, out) {
      var hits = (out && out.hits) || []

      hits = hits.map(function (hit) {
        return hit.document
      })

      done(null, hits)
    })
  }


  // Insert a document into the search index.
  function search_insert (msg, done) {
    index.add([{
      id: msg.id,
      text: msg.text,
      user: msg.user,
      when: msg.when
    }], {}, done)
  }


  // Initialize the plugin. This is the standard mechanism to initialize a Seneca
  // plugin - by defining a special pattern of the form init:<plugin-name>.
  function init (msg, done) {
    Search({
      indexes: Levelup('si', {
        db: Memdown, 
        valueEncoding: 'json'
      })
    }, function(err, si) {
      if (err) return done(err)
      index = si
      done()
    })
  }
}


================================================
FILE: docker/index/index-service.js
================================================
var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[3] || '').split(',')
var SILENT = process.env.SILENT || process.argv[4] || 'true'


require('seneca')({
  tag: 'index',
  internal: {logger: require('seneca-demo-logger')},
  debug: {short_logs: true}
})
    //.use('zipkin-tracer', {sampling:1})

  .use('index-logic')

  .add('info:entry', function(msg,done){
    delete msg.info
    this.act('search:insert',msg,done)
  })

  .use('mesh',{
    listen:[
      {pin: 'search:*'},
      {pin: 'info:entry', model:'observe'}
    ],
    bases: BASES,
    host: HOST,
    sneeze: {
      silent: JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  })

  .ready(function(){
    console.log(this.id)
  })


================================================
FILE: docker/mine/Dockerfile
================================================
FROM shared

ADD mine-service.js .
ADD www www

CMD ["node", "mine-service.js"]



================================================
FILE: docker/mine/Makefile
================================================
container :
	cp ../../mine/mine-service.js .
	cp -r ../../mine/www .
	docker build -t mine .
	docker images | grep mine

run-single :
	docker service create --replicas 1 --network ramanujan  --name mine -e HOST=eth0 -e BASES=base0:39000,base1:39000 mine

rm-single :
	docker service rm mine

clean :
	rm -f *~
	rm -f *.js
	rm -f *.json

.PHONY : container clean


================================================
FILE: docker/mine/mine-service.js
================================================
"use strict"

var PORT = process.env.PORT || process.argv[2] || 0
var HOST = process.env.HOST || process.argv[3] || 0
var BASES = (process.env.BASES || process.argv[4] || '').split(',')
var SILENT = process.env.SILENT || process.argv[5] || 'true'

var hapi       = require('hapi')
var chairo     = require('chairo')
var vision     = require('vision')
var inert      = require('inert')
var handlebars = require('handlebars')
var _          = require('lodash')
var moment     = require('moment')
var Seneca     = require('seneca')
var Rif        = require('rif')


var server = new hapi.Server()
var rif = Rif()


var host = rif(HOST) || HOST


server.connection({
    port: PORT,
    host: host
})

server.register( vision )
server.register( inert )

server.register({
  register:chairo,
  options:{
    seneca: Seneca({
      tag: 'mine',
      internal: {logger: require('seneca-demo-logger')},
      debug: {short_logs:true}
    })
      //.use('zipkin-tracer', {sampling:1})
      .use('entity')
  }
})

server.register({
  register: require('wo'),
  options:{
    bases: BASES,
    route: [
        {path: '/mine/{user}'},
    ],
    sneeze: {
      host: host,
      silent: JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  }
})


server.views({
  engines: { html: handlebars },
  path: __dirname + '/www',
  layout: true
})


server.route({
  method: 'GET', path: '/mine/{user}',
  handler: function( req, reply )
  {
    server.seneca.act(
      'store:list,kind:entry',
      {user:req.params.user},
      function( err, entrylist ) {
        if(err) {
          entrylist = []
        }

        reply.view('mine',{
          user: req.params.user,
          entrylist: _.map(entrylist,function(entry){
            entry.when = moment(entry.when).fromNow()
            return entry
          })
        })
      })
  }
})


server.seneca.use('mesh',{
    bases:BASES,
    host:host
})

server.start(function(){
  console.log('mine',server.info.host,server.info.port)
})




================================================
FILE: docker/mine/www/home.html
================================================
<form action="/api/post/{{user}}" method="post">
<input type="text" name="text">
<input type="hidden" name="from" value="/{{user}}">
<input type="submit" value="post">
</form>

<br>

{{#each entrylist}}
<div class="entry">
<form action="/api/follow/{{../user}}" method="post">
<b><a href="/{{this.user}}">@{{this.user}}</a></b> <small>{{this.when}}</small>
<input type="hidden" name="from" value="/{{../user}}">
<input type="hidden" name="user" value="{{this.user}}">
{{#if can_follow}}
<input type="submit" value="follow">
{{/if}}
<br>
<div class="text">
{{this.text}}
</div>
</form>
</div>
{{/each}}




================================================
FILE: docker/mine/www/layout.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Microblog</title>
  <link rel="stylesheet" href="/res/site.css">
</head>
<body>

  <div class="header">
    <a href="/{{user}}">Home</a>
    <a href="/mine/{{user}}" class="nav_active">Mine</a> 
    <a href="/search/{{user}}">Search</a>
  </div>

  <div class="container">
    {{{ content }}}
  </div>

</body>
</html>



================================================
FILE: docker/mine/www/mine.html
================================================
<form action="/api/post/{{user}}" method="post">
<input type="text" name="text">
<input type="hidden" name="from" value="/mine/{{user}}">
<input type="submit" value="post">
</form>

<br>

{{#each entrylist}}
<div class="entry">
<small>{{this.when}}</small>
<br>
<div class="text">
{{this.text}}
</div>
</form>
</div>
{{/each}}




================================================
FILE: docker/post/Dockerfile
================================================
FROM shared

ADD post-logic.js .
ADD post-service.js .

CMD ["node", "post-service.js"]



================================================
FILE: docker/post/Makefile
================================================
container :
	cp ../../post/post-*.js .
	docker build -t post .
	docker images | grep post

run-single :
	docker service create --replicas 1 --network ramanujan --name post -e HOST=@eth0 -e BASES=base0:39000,base1:39000 post

rm-single :
	docker service rm post


clean :
	rm -f *~
	rm -f *.js
	rm -f *.json

.PHONY : container clean


================================================
FILE: docker/post/post-logic.js
================================================
module.exports = function post (options) {
  var seneca = this

  seneca.add('post:entry', function(msg, done) {
    var entry = this.util.clean(msg)
    delete entry.post

    entry.when = Date.now()

    this.act('store:save,kind:entry', entry, function(err,entry) {
	done(err)

      if( !err ) {
        this.act('info:entry',entry.data$())
      }
    })
  })
}


================================================
FILE: docker/post/post-service.js
================================================
var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[3] || '').split(',')
var SILENT = process.env.SILENT || process.argv[4] || 'true'


require('seneca')({
  tag: 'post',
  internal: {logger: require('seneca-demo-logger')},
  debug: { short_logs: true }
})
  //.use('zipkin-tracer', {sampling:1})
  .use('entity')
  .use('post-logic')

  .use('mesh',{
    pin: 'post:*',
    bases: BASES,
    host: HOST,
    sneeze: {
      silent: JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  })

  .ready(function(){
    console.log(this.id)
  })


================================================
FILE: docker/ramanujan.yml
================================================

version: '3'

networks:
  ramanujan:
    driver: overlay

services:
  base0:
    image: base
    environment:
      TAG: base0
      PORT: 39000
      HOST: base0
      BASES: base0:39000,base1:39001
      SILENT: 'false'
    networks:
      ramanujan:
        aliases: 
          - base0
    deploy:
      replicas: 1

  base1:
    image: base
    environment:
      TAG: base1
      PORT: 39001
      HOST: base1
      BASES: base0:39000,base1:39001
      SILENT: 'false'
    networks:
      ramanujan:
        aliases: 
          - base1
    deploy:
      replicas: 1

  repl:
    image: repl
    environment:
      REPL_HOST: 0.0.0.0
      HOST: '@eth2'
      BASES: base0:39000,base1:39001
      SILENT: 'false'
    ports:
      - '10001:10001'
    networks:
      - ramanujan
    deploy:
      replicas: 1


  front:
    image: front
    environment:
      HOST: 'eth2'
      BASES: base0:39000,base1:39001
      SILENT: 'false'
    ports:
      - '8000:8000'
    networks:
      - ramanujan
    deploy:
      replicas: 1


  home:
    image: home
    environment:
      HOST: 'eth0'
      BASES: base0:39000,base1:39001
      SILENT: 'false'
    networks:
      - ramanujan
    deploy:
      replicas: 1


  mine:
    image: mine
    environment:
      HOST: 'eth0'
      BASES: base0:39000,base1:39001
      SILENT: 'false'
    networks:
      - ramanujan
    deploy:
      replicas: 1


  search:
    image: search
    environment:
      HOST: 'eth0'
      BASES: base0:39000,base1:39001
      SILENT: 'false'
    networks:
      - ramanujan
    deploy:
      replicas: 1


  api:
    image: api
    environment:
      HOST: 'eth0'
      BASES: base0:39000,base1:39001
      SILENT: 'false'
    networks:
      - ramanujan
    deploy:
      replicas: 1


  entry-store:
    image: entry-store
    environment:
      HOST: '@eth0'
      BASES: base0:39000,base1:39001
      SILENT: 'false'
    networks:
      - ramanujan
    deploy:
      replicas: 1


  entry-cache:
    image: entry-cache
    environment:
      HOST: '@eth0'
      BASES: base0:39000,base1:39001
      SILENT: 'false'
    networks:
      - ramanujan
    deploy:
      replicas: 1


  index:
    image: index
    environment:
      HOST: '@eth0'
      BASES: base0:39000,base1:39001
      SILENT: 'false'
    networks:
      - ramanujan
    deploy:
      replicas: 1


  reserve:
    image: reserve
    environment:
      HOST: '@eth0'
      BASES: base0:39000,base1:39001
      SILENT: 'false'
    networks:
      - ramanujan
    deploy:
      replicas: 1


  fanout:
    image: fanout
    environment:
      HOST: '@eth0'
      BASES: base0:39000,base1:39001
      SILENT: 'false'
    networks:
      - ramanujan
    deploy:
      replicas: 1


  follow:
    image: follow
    environment:
      HOST: '@eth0'
      BASES: base0:39000,base1:39001
      SILENT: 'false'
    networks:
      - ramanujan
    deploy:
      replicas: 1


  post:
    image: post
    environment:
      HOST: '@eth0'
      BASES: base0:39000,base1:39001
      SILENT: 'false'
    networks:
      - ramanujan
    deploy:
      replicas: 1


  timeline:
    image: timeline
    environment:
      HOST: '@eth0'
      BASES: base0:39000,base1:39001
      SILENT: 'false'
    networks:
      - ramanujan
    deploy:
      replicas: 1


  timeline-shard-0:
    image: timeline-shard
    environment:
      SHARD: 0,
      HOST: '@eth0'
      BASES: base0:39000,base1:39001
      SILENT: 'false'
    networks:
      - ramanujan
    deploy:
      replicas: 1


  timeline-shard-1:
    image: timeline-shard
    environment:
      SHARD: 1,
      HOST: '@eth0'
      BASES: base0:39000,base1:39001
      SILENT: 'false'
    networks:
      - ramanujan
    deploy:
      replicas: 1


================================================
FILE: docker/repl/Dockerfile
================================================
FROM shared

ADD repl-service.js .
ADD monitor.js .

EXPOSE 10001

CMD ["node", "repl-service.js"]



================================================
FILE: docker/repl/Makefile
================================================
container :
	cp ../../repl/repl-service.js .
	cp ../../monitor/monitor.js .
	docker build -t repl .
	docker images | grep repl

run-single :
	docker service create --replicas 1 --network ramanujan -p 10001:10001 --name repl -e REPL_HOST=0.0.0.0  -e HOST=@eth2 -e BASES=base0:39000,base1:39000 repl


rm-single :
	docker service rm repl


clean :
	rm -f *~
	rm -f *.js
	rm -f *.json

.PHONY : container clean


================================================
FILE: docker/repl/monitor.js
================================================
var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[3] || '127.0.0.1:39000,127.0.0.1:39001').split(',')

require('seneca')()//({log: 'silent'})
  .use('mesh',{
    bases: BASES,
    host: HOST,
    monitor: true,
    tag: null
  })


================================================
FILE: docker/repl/repl-service.js
================================================
var REPL_PORT = parseInt(process.env.REPL_PORT || process.argv[2] || 10001)
var REPL_HOST = process.env.REPL_HOST || process.argv[3] || '127.0.0.1'
var HOST = process.env.HOST || process.argv[4] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[5] || '').split(',')
var SILENT = process.env.SILENT || process.argv[6] || 'true'


var repl = require('seneca-repl');


var seneca = require('seneca')({
  tag: 'repl',
  internal: {logger: require('seneca-demo-logger')},
  debug: {short_logs:true}
})
//.use('zipkin-tracer', {sampling:1})
.use('mesh',{
  tag: null, // ensures membership of all tagged meshes
  bases: BASES,
  host: HOST,
  make_entry: function( entry ) {
    if( 'wo' === entry.tag$ ) {
      return {
        route: JSON.stringify(entry.route),
        host: entry.host,
        port: entry.port,
        identifier: entry.identifier$
      }
    }
  },
  sneeze:{
    silent: JSON.parse(SILENT),
    swim: {interval: 1111}
  }
})
.use(repl)
.ready(function () {
  seneca.repl({
    port: REPL_PORT,
    host: REPL_HOST,
      alias: {
      m: 'role:mesh,get:members'
    }
  })
})


================================================
FILE: docker/reserve/Dockerfile
================================================
FROM shared

ADD reserve-logic.js .
ADD reserve-service.js .

CMD ["node", "reserve-service.js"]



================================================
FILE: docker/reserve/Makefile
================================================
container :
	cp ../../reserve/reserve-*.js .
	docker build -t reserve .
	docker images | grep reserve

run-single :
	docker service create --replicas 1 --network ramanujan --name reserve -e HOST=@eth0 -e BASES=base0:39000,base1:39000 reserve

rm-single :
	docker service rm reserve


clean :
	rm -f *~
	rm -f *.js
	rm -f *.json

.PHONY : container clean


================================================
FILE: docker/reserve/reserve-logic.js
================================================
var _ = require('lodash')

module.exports = function follow (options) {
  var seneca = this

  var interval = options.interval || 22
  var expires  = options.expires || 1111


  seneca.add('reserve:create', reserve_create)
  seneca.add('reserve:read', reserve_read)
  seneca.add('reserve:remove', reserve_remove)
  seneca.add('reserve:state', reserve_state)


  var reservations = {}
  
  
  setInterval(function () {
    var now = Date.now()
    Object.keys(reservations).forEach(function (key) {
      var when = reservations[key]

      if (expires < now - when) {
        delete reservations[key]
      }
    })
  }, interval)


  function reserve_create(msg, reply) {
    var key = msg.key
    
    if (reservations[key]) {
      return reply(null, {ok:false})
    }

    reservations[key] = Date.now()
    return reply(null, {ok:true})
  }


  function reserve_read(msg, reply) {
    return reply(null, {ok: !!reservations[msg.key]})
  }


  function reserve_remove(msg, reply) {
    var found = !!reservations[msg.key]
    delete reservations[msg.key]
    return reply(null, {ok:found})
  }


  function reserve_state(msg, reply) {
    return reply(null, reservations)
  }
}
 


================================================
FILE: docker/reserve/reserve-service.js
================================================
var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[3] || '').split(',')
var SILENT = process.env.SILENT || process.argv[4] || 'true'

require('seneca')({
  tag: 'reserve',
  internal: {logger: require('seneca-demo-logger')},
  debug: {short_logs: true}
})
  //.use('zipkin-tracer', {sampling:1})
  .use('reserve-logic')

  .use('mesh',{
    pin: 'reserve:*',
    bases: BASES,
    host: HOST,
    sneeze: {
      silent:JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  })

  .ready(function(){
    console.log(this.id)
  })


================================================
FILE: docker/search/Dockerfile
================================================
FROM shared

ADD search-service.js .
ADD www www

CMD ["node", "search-service.js"]



================================================
FILE: docker/search/Makefile
================================================
container :
	cp ../../search/search-service.js .
	cp -r ../../search/www .
	docker build -t search .
	docker images | grep search

run-single :
	docker service create --replicas 1 --network ramanujan  --name search -e HOST=eth0 -e BASES=base0:39000,base1:39000 search

rm-single :
	docker service rm search

clean :
	rm -f *~
	rm -f *.js
	rm -f *.json

.PHONY : container clean


================================================
FILE: docker/search/search-service.js
================================================
"use strict"

var PORT = process.env.PORT || process.argv[2] || 0
var HOST = process.env.HOST || process.argv[3] || 0
var BASES = (process.env.BASES || process.argv[4] || '').split(',')
var SILENT = process.env.SILENT || process.argv[5] || 'true'


var hapi       = require('hapi')
var chairo     = require('chairo')
var vision     = require('vision')
var inert      = require('inert')
var handlebars = require('handlebars')
var _          = require('lodash')
var moment     = require('moment')
var Seneca     = require('seneca')
var Rif        = require('rif')


var server = new hapi.Server()
var rif = Rif()


var host = rif(HOST) || HOST


server.connection({
    port: PORT,
    host: host
})


server.register( vision )
server.register( inert )

server.register({
  register:chairo,
  options:{
    seneca: Seneca({
      tag: 'search',
      internal: {logger: require('seneca-demo-logger')},
      debug: {short_logs:true}
    })
	  //.use('zipkin-tracer', {sampling:1})
  }
})

server.register({
  register: require('wo'),
  options:{
    bases: BASES,
    route: [
        {method: ['GET','POST'], path: '/search/{user}'},
    ],
    sneeze: {
      host: host,
      silent: JSON.parse(SILENT)
    }
  }
})


server.views({
  engines: { html: handlebars },
  path: __dirname + '/www',
  layout: true
})


server.route({
  method: ['GET','POST'],
  path: '/search/{user}',
  handler: function( req, reply )
  {
    var query
      = (req.query ? (null == req.query.query ? '' : ' '+req.query.query) : '')
      + (req.payload ? (null == req.payload.query ? '' : ' '+req.payload.query) : '')

    query = query.replace(/^ +/,'')
    query = query.replace(/ +$/,'')

    server.seneca.act(
      'follow:list,kind:following',
      {user:req.params.user},
      function(err,following){
        if( err ) {
          following = []
        }

        this.act(
          'search:query',
          {query: query },
          function( err, entrylist ) {
            if(err) {
              this.log.warn(err)
              entrylist = []
            }

            reply.view('search',{
              query: encodeURIComponent(query),
              user: req.params.user,
              entrylist: _.map(entrylist,function(entry){
                entry.when = moment(entry.when).fromNow()
                entry.can_follow =
                  req.params.user != entry.user &&
                  !_.includes(following,entry.user)
                return entry
              })
            })
          })
      })
  }
})


server.seneca.use('mesh',{
  bases:BASES,
  host:host,
  sneeze:{
    silent: JSON.parse(SILENT),
    swim: {interval: 1111}
  }
})

server.start(function(){
  console.log('search',server.info.uri)
})



================================================
FILE: docker/search/www/home.html
================================================
<form action="/api/post/{{user}}" method="post">
<input type="text" name="text">
<input type="hidden" name="from" value="/{{user}}">
<input type="submit" value="post">
</form>

<br>

{{#each entrylist}}
<div class="entry">
<form action="/api/follow/{{../user}}" method="post">
<b><a href="/{{this.user}}">@{{this.user}}</a></b> <small>{{this.when}}</small>
<input type="hidden" name="from" value="/{{../user}}">
<input type="hidden" name="user" value="{{this.user}}">
{{#if can_follow}}
<input type="submit" value="follow">
{{/if}}
<br>
<div class="text">
{{this.text}}
</div>
</form>
</div>
{{/each}}




================================================
FILE: docker/search/www/layout.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Microblog</title>
  <link rel="stylesheet" href="/res/site.css">
</head>
<body>

  <div class="header">
    <a href="/{{user}}">Home</a>
    <a href="/mine/{{user}}">Mine</a> 
    <a href="/search/{{user}}" class="nav_active">Search</a>
  </div>

  <div class="container">
    {{{ content }}}
  </div>

</body>
</html>



================================================
FILE: docker/search/www/search.html
================================================
<form action="/search/{{user}}" method="post">
<input type="text" name="query">
<input type="submit" value="search">
</form>

<br>

{{#each entrylist}}
<div class="entry">
<form action="/api/follow/{{../user}}" method="post">
<b><a href="/{{this.user}}">@{{this.user}}</a></b> <small>{{this.when}}</small>
<input type="hidden" name="from" value="/search/{{../user}}?query={{{../query}}}">
<input type="hidden" name="user" value="{{this.user}}">
{{#if can_follow}}
<input type="submit" value="follow">
{{/if}}
<br>
<div class="text">
{{this.text}}
</div>
</form>
</div>
{{/each}}




================================================
FILE: docker/shared/Dockerfile
================================================

FROM mhart/alpine-node:4

RUN apk add --no-cache make gcc g++ python git

ADD package.json /

RUN npm install






================================================
FILE: docker/shared/Makefile
================================================

container :
	cp ../../package.json .
	docker build -t shared .
	docker images | grep shared

clean :
	rm -f *~
	rm -f *.json

.PHONY : container clean



================================================
FILE: docker/shared/package.json
================================================
{
  "name": "ramanujan",
  "version": "0.0.1",
  "description": "ramanujan",
  "main": "ramanujan.js",
  "scripts": {
    "test": "lab -v */test -I nil"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/senecajs/ramanujan.git"
  },
  "keywords": [
    "seneca",
    "demo",
    "ramanujan"
  ],
  "author": "Richard Rodger richardrodger.com",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/senecajs/ramanujan/issues"
  },
  "homepage": "https://github.com/senecajs/ramanujan",
  "dependencies": {
    "chairo": "2.2.1",
    "handlebars": "4.0.5",
    "hapi": "15.0.3",
    "inert": "4.0.2",
    "levelup": "1.3.3",
    "lodash": "4.15.0",
    "memdown": "1.2.0",
    "moment": "2.14.1",
    "search-index": "0.8.15",
    "seneca": "3.3.0",
    "seneca-balance-client": "0.6.0",
    "seneca-basic": "0.5.0",
    "seneca-demo-logger": "0.2.0",
    "seneca-entity": "1.3.0",
    "seneca-mesh": "0.10.0",
    "seneca-repl": "0.3.0",
    "seneca-zipkin-tracer": "0.1.0",
    "sneeze": "https://github.com/rjrodger/sneeze#diagnostic",
    "vision": "4.1.0",
    "wo": "0.8.0",
    "rif": "0.3.0"
  },
  "devDependencies": {
    "code": "3.0.2",
    "lab": "11.0.1"
  }
}


================================================
FILE: docker/timeline/Dockerfile
================================================
FROM shared

ADD timeline-shard-service.js .

CMD ["node", "timeline-shard-service.js"]



================================================
FILE: docker/timeline/Makefile
================================================
container :
	cp ../../timeline/timeline-shard-service.js .
	docker build -t timeline .
	docker images | grep timeline

run-single :
	docker service create --replicas 1 --network ramanujan --name timeline -e HOST=@eth0 -e BASES=base0:39000,base1:39000 timeline

rm-single :
	docker service rm timeline


clean :
	rm -f *~
	rm -f *.js
	rm -f *.json

.PHONY : container clean


================================================
FILE: docker/timeline/timeline-shard-service.js
================================================
var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[3] || '').split(',')
var SILENT = process.env.SILENT || process.argv[5] || 'true'


var _ = require('lodash')

function resolve_shard(user) {
  return user.charCodeAt(0) % 2
}

require('seneca')({
  tag: 'timeline-shard',
  internal: {logger: require('seneca-demo-logger')},
  debug: {short_logs:true}
})
    //.use('zipkin-tracer', {sampling:1})

  .add('timeline:list',function(msg,done){
    var shard = resolve_shard(msg.user)
    this.act({shard:shard},msg,done)
  })

  .add('timeline:insert',function(msg,done){
    var seneca = this
    done()

    var shards = [[],[]]

    _.each(msg.users,function(user){
      shards[resolve_shard(user)].push(user)
    })

    _.each(shards,function(users,shard){
      if( 0 < users.length ) {
        seneca.act({
          shard: shard,
          users: users,
        }, msg)
      }
    })
  })

  .use('mesh',{
    pin: 'timeline:*',
      bases: BASES,
      host: HOST,
      sneeze:{
        silent: JSON.parse(SILENT),
        swim: {interval: 1111}
      }
  })

  .ready(function(){
    console.log(this.id)
  })


================================================
FILE: docker/timeline-shard/Dockerfile
================================================
FROM shared

ADD timeline-service.js .
ADD timeline-logic.js .

CMD ["node", "timeline-service.js"]



================================================
FILE: docker/timeline-shard/Makefile
================================================
container :
	cp ../../timeline/timeline-logic.js .
	cp ../../timeline/timeline-service.js .
	docker build -t timeline-shard .
	docker images | grep timeline-shard

run-single-0 :
	docker service create --replicas 1 --network ramanujan --name timeline-shard-0 -e SHARD=0 -e HOST=@eth0 -e BASES=base0:39000,base1:39000 timeline-shard

run-single-1 :
	docker service create --replicas 1 --network ramanujan --name timeline-shard-1 -e SHARD=1 -e HOST=@eth0 -e BASES=base0:39000,base1:39000 timeline-shard

rm-single-0 :
	docker service rm timeline-shard-0

rm-single-1 :
	docker service rm timeline-shard-1


clean :
	rm -f *~
	rm -f *.js
	rm -f *.json

.PHONY : container clean


================================================
FILE: docker/timeline-shard/timeline-logic.js
================================================
'use strict'

var _ = require('lodash')

module.exports = function timeline (options) {
  var seneca = this


  seneca.add('timeline:insert', insert)
  seneca.add('timeline:list', list)


  function insert (msg, done) {
    var seneca = this
    done()

    var entry = {
      user: msg.user,
      text: msg.text,
      when: msg.when,
    }

    var users = _.clone(msg.users)
    var index = -1

    do_user()

    function do_user(err) {
      // try to complete the entire list, despite individual errors
      if( err ) {
        seneca.log.error(err)
      }

      ++index
      if( users.length <= index ) return

      insert_entry( users[index], true, do_user )
    }


    function insert_entry( user, create, next ) {
      seneca
        .make('timeline')
        .load$(user,function(err,timeline){
          if(err) return next(err)

          if (timeline) {
            do_insert(timeline, next)
          }
          else if (create) {
            this.act(
              'reserve:create', 
              {key: 'timeline/'+user}, 
              function (err, status) {
                if( err ) return next(err)
            
                if( !status.ok ) {
                  return insert_entry(user, false, next)
                }

                this
                  .make('timeline',{id$:user, entrylist:[]})
                  .save$( function(err,timeline) {
                    if( err ) return next(err)

                    do_insert(timeline, function (err) {
                      if( err ) return next(err)

                      this.act('reserve:remove', {key: 'timeline/'+user})
                    })
                  })
              })
          }

          function do_insert (timeline, next) {
            timeline.entrylist.push(entry)
            timeline.entrylist.sort(function(a,b){
              return b.when - a.when
            })

            timeline.save$(next)
          }
        })
    }
  }


  function list (msg, done) {
      this.act('follow:list,kind:following',{user:msg.user,default$:{}},function(err,following){
      if( err ) return done(err)

      this
        .make('timeline')
        .load$(msg.user,function(err,timeline){
          var entrylist = (timeline ? timeline.entrylist : [])
          _.each(entrylist,function(entry){
            entry.can_follow = 
              entry.user !== msg.user && 
              !_.includes(following,entry.user)
          })

          done(err,entrylist)
        })
    })
  }
}


================================================
FILE: docker/timeline-shard/timeline-service.js
================================================
var SHARD = process.env.SHARD || process.argv[2] || 0
var HOST = process.env.HOST || process.argv[3] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[4] || '').split(',')
var SILENT = process.env.SILENT || process.argv[5] || 'true'

require('seneca')({
  tag: 'timeline'+SHARD,
  internal: {logger: require('seneca-demo-logger')},
  debug: {short_logs:true}
})
  .use('zipkin-tracer', {sampling:1})
  .use('entity')
  .use('timeline-logic')
  .use('mesh',{
    pin: 'timeline:*,shard:'+SHARD,
    bases: BASES,
    host: HOST,
    sneeze: {
      silent:JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  })

  .ready(function(){
    console.log(this.id)
  })


================================================
FILE: entry-cache/entry-cache-logic.js
================================================
'use strict'

var _ = require('lodash')

module.exports = function entry_cache (options) {
  var seneca = this

  var cache = {}

  seneca.add('store:save,kind:entry', function(msg, done) {
    delete cache[msg.user]
    msg.cache = true
    this.act(msg, done)
  })


  seneca.add('store:list,kind:entry', function(msg, done) {
    if( cache[msg.user] ) {
      return done( null, cache[msg.user] )
    }

    msg.cache = true

    this.act(msg, function(err,list){
      if(err) return done(err)
      cache[msg.user] = list
      done(null,list)
    })
  })
}


================================================
FILE: entry-cache/entry-cache-service.js
================================================
var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[3] || '').split(',')
var SILENT = process.env.SILENT || process.argv[4] || 'true'

require('seneca')({
  tag:'entry-cache',
  internal: {logger: require('seneca-demo-logger')},
  debug: {short_logs:true}
})
    //.use('zipkin-tracer', {sampling:1})
  .use('basic')
  .use('entity')
  .use('entry-cache-logic')
  .use('mesh',{
    pin: 'store:*,kind:entry',
    bases: BASES,
    host: HOST,
    sneeze: {
      silent: JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  })
  .ready(function(){
    console.log(this.id)
  })


================================================
FILE: entry-cache/test/entry-cache-test.js
================================================
// Unit test for the entry-cache microservice.
// Uses https://github.com/hapijs/lab but easy to refactor for other unit testers.

// The utility function test_seneca constructs an instance of Seneca
// suitable for test execution, using the seneca.test() method.

var Lab = require('lab')
var Code = require('code')
var Seneca = require('seneca')

var lab = exports.lab = Lab.script()
var describe = lab.describe
var it = lab.it
var expect = Code.expect

// A suite of unit tests for this microservice.
describe('entry-cache', function () {

  // A unit test (the test callback is named 'fin' to distinguish it from others).
  it('save-list', function (fin) {

    // Create a Seneca instance for testing.
    var seneca = test_seneca(fin)

    var data = {}
    var miss = {}

    seneca
    .add('store:save,kind:entry,cache:true', function (msg, reply) {
      var entry = this.make('entry', {
        when: msg.when,
        user: msg.user,
        text: msg.text
      })

      data[msg.user] = data[msg.user] || []
      data[msg.user].push(entry)
      reply(null, entry)
    })

    .add('store:list,kind:entry,cache:true', function (msg, reply) {
      miss[msg.user] = miss[msg.user] || 0
      miss[msg.user]++

      reply(null, data[msg.user])
    })


    // Gate the execution of actions for this instance. Gated actions are executed
    // in sequence and each action waits for the previous one to complete. Gating
    // is not required, but avoids excessive callbacks in the unit test code.
    seneca
      .gate()

      .act({
        store: 'save',
        kind: 'entry',
        when: Date.now(),
        user: 'u0',
        text: 't0'

        // Because test mode is active, it is not necessary to handle
        // callback errors. These are passed directly to the 'fin' callback.
      }, function (ignore, entry) {
        expect(entry.user).to.equal('u0')
        expect(entry.text).to.equal('t0')

        expect(data[entry.user].length).to.equal(1)
        expect(data[entry.user][0].text).to.equal('t0')
      })

      .act({
        store: 'list',
        kind: 'entry',
        user: 'u0'
      }, function (ignore, list) {
        expect(list.length).to.equal(1)
        expect(list[0].text).to.equal('t0')

        expect(miss['u0']).to.equal(1)
      })

      .act({
        store: 'list',
        kind: 'entry',
        user: 'u0'
      }, function (ignore, list) {
        expect(list.length).to.equal(1)
        expect(list[0].text).to.equal('t0')

        expect(miss['u0']).to.equal(1)
      })


    // Second save invalidates cache and miss count confirms this.
      .act({
        store: 'save',
        kind: 'entry',
        when: Date.now(),
        user: 'u0',
        text: 't1'

        // Because test mode is active, it is not necessary to handle
        // callback errors. These are passed directly to the 'fin' callback.
      }, function (ignore, entry) {
        expect(entry.user).to.equal('u0')
        expect(entry.text).to.equal('t1')

        expect(data[entry.user].length).to.equal(2)
        expect(data[entry.user][0].text).to.equal('t0')
        expect(data[entry.user][1].text).to.equal('t1')
      })

      .act({
        store: 'list',
        kind: 'entry',
        user: 'u0'
      }, function (ignore, list) {
        expect(list.length).to.equal(2)
        expect(list[0].text).to.equal('t0')
        expect(list[1].text).to.equal('t1')

        expect(miss['u0']).to.equal(2)
      })

      .act({
        store: 'list',
        kind: 'entry',
        user: 'u0'
      }, function (ignore, list) {
        expect(list.length).to.equal(2)
        expect(list[0].text).to.equal('t0')
        expect(list[1].text).to.equal('t1')

        expect(miss['u0']).to.equal(2)
      })

    // Once all the tests are complete, invoke the test callback
      .ready(fin)
  })
})


// Construct a Seneca instance suitable for unit testing
function test_seneca (fin) {


  return Seneca({log: 'test'})

  // activate unit test mode. Errors provide additional stack tracing context.
  // The fin callback is called when a error occurs anywhere.
    .test(fin)
  
  // Load the plugin dependencies of the microservice
    .use('basic')
    .use('entity')

  // Load the microservice business logic
    .use(require('../entry-cache-logic'))
}


================================================
FILE: entry-store/entry-store-logic.js
================================================
module.exports = function entry_store (options) {
  var seneca = this

  seneca.add('store:save,kind:entry', function(msg, done) {
    this
      .make('entry', {
        when: msg.when,
        user: msg.user,
        text: msg.text
      })
      .save$(function(err, entry) {
        if(err) return done(err)

        this.act(
          {
            timeline: 'insert',
            users: [msg.user],
          }, 
          entry, 
          function(err) {
            return done(err, entry)
          })
      })
  })

  seneca.add('store:list,kind:entry', function(msg, done) {
    this
      .make('entry')
      .list$( {user: msg.user}, function(err, list) {
        if(err) return done(err)

        list.reverse( function(a, b) {
          return a.when - b.when
        })

        done( null, list )
      })
  })
}


================================================
FILE: entry-store/entry-store-service.js
================================================
var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[3] || '').split(',')
var SILENT = process.env.SILENT || process.argv[4] || 'true'

require('seneca')({
  tag: 'entry-store',
  internal: {logger: require('seneca-demo-logger')},
  debug: {short_logs: true}
})
    //.use('zipkin-tracer', {sampling:1})
  .use('basic')
  .use('entity')
  .use('entry-store-logic')
  .use('mesh',{
    pin: 'store:*,kind:entry,cache:true',
    bases: BASES,
    host: HOST,
    sneeze:{
      silent: JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  })
  .ready(function(){
    console.log(this.id)
  })


================================================
FILE: entry-store/test/entry-store-test.js
================================================
// Unit test for the entry-store microservice.
// Uses https://github.com/hapijs/lab but easy to refactor for other unit testers.

// The utility function test_seneca constructs an instance of Seneca
// suitable for test execution, using the seneca.test() method.

var Lab = require('lab')
var Code = require('code')
var Seneca = require('seneca')

var lab = exports.lab = Lab.script()
var describe = lab.describe
var it = lab.it
var expect = Code.expect

// A suite of unit tests for this microservice.
describe('entry-store', function () {

  // A unit test (the test callback is named 'fin' to distinguish it from others).
  it('add-entry', function (fin) {

    // Create a Seneca instance for testing.
    var seneca = test_seneca(fin)

    // Gate the execution of actions for this instance. Gated actions are executed
    // in sequence and each action waits for the previous one to complete. Gating
    // is not required, but avoids excessive callbacks in the unit test code.
    seneca
      .gate()

    // Send an action, and validate the response.
      .act({
        store: 'save',
        kind: 'entry',
        when: Date.now(),
        user: 'u0',
        text: 't0'

        // Because test mode is active, it is not necessary to handle
        // callback errors. These are passed directly to the 'fin' callback.
      }, function (ignore, entry) {
        expect(entry.user).to.equal('u0')
        expect(entry.text).to.equal('t0')
      })

      .act({
        store: 'save',
        kind: 'entry',
        when: Date.now(),
        user: 'u0',
        text: 't1'
      }, function (ignore, entry) {
        expect(entry.user).to.equal('u0')
        expect(entry.text).to.equal('t1')
      })

      .act({
        store: 'list',
        kind: 'entry',
        user: 'u0'
      }, function (ignore, list) {
        expect(list.length).to.equal(2)
        expect(list[0].text).to.equal('t1')
        expect(list[1].text).to.equal('t0')
      })

    // Once all the tests are complete, invoke the test callback
      .ready(fin)
  })
})


// Construct a Seneca instance suitable for unit testing
function test_seneca (fin) {
  return Seneca({log: 'test'})

  // activate unit test mode. Errors provide additional stack tracing context.
  // The fin callback is called when a error occurs anywhere.
    .test(fin)

  // Load the plugin dependencies of the microservice
    .use('basic')
    .use('entity')

  // Load the microservice business logic
    .use(require('../entry-store-logic'))
  
  // IMPORTANT! Provide mocks for any message patterns that the microservice
  // depends on. In production these are provided by other microservices.
  // To define a mock message, just add an action for the message pattern.
    .add('timeline:insert', function (msg, reply) {
      reply()
    })
}


================================================
FILE: fanout/fanout-logic.js
================================================
'use strict'

var _ = require('lodash')

module.exports = function fanout (options) {
  var seneca = this

  seneca.add('fanout:entry', function(msg, done) {
    done()

    var entry = this.util.clean(msg)
    delete entry.fanout

    this.act('follow:list,kind:followers',{user:entry.user},function(err,userlist){
      if(err) return

      if( userlist && 0 < userlist.length ) {
        this.act({
          timeline: 'insert',
          users: userlist,
        }, entry)
      }
    })
  })
}


================================================
FILE: fanout/fanout-service.js
================================================
var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[3] || '').split(',')
var SILENT = process.env.SILENT || process.argv[4] || 'true'

require('seneca')({
  tag: 'fanout',
  internal: {logger: require('seneca-demo-logger')},
  debug: {short_logs: true}
})
    //.use('zipkin-tracer', {sampling:1})
  .use('fanout-logic')

  .add('info:entry', function(msg,done){
    delete msg.info
    this.act('fanout:entry',msg,done)
  })

  .use('mesh',{
    listen:[
      {pin: 'fanout:*'},
      {pin: 'info:entry', model:'observe'}
    ],
    bases: BASES,
    host: HOST,
    sneeze: {
      silent: JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  })

  .ready(function(){
    console.log(this.id)
  })


================================================
FILE: fanout/test/fanout-test.js
================================================
// Unit test for the fanout microservice.
// Uses https://github.com/hapijs/lab but easy to refactor for other unit testers.

// The utility function test_seneca constructs an instance of Seneca
// suitable for test execution, using the seneca.test() method.

var Lab = require('lab')
var Code = require('code')
var Seneca = require('seneca')

var lab = exports.lab = Lab.script()
var describe = lab.describe
var it = lab.it
var expect = Code.expect

// A suite of unit tests for this microservice.
describe('fanout', function () {

  // A unit test (the test callback is named 'fin' to distinguish it from others).
  it('entry', function (fin) {

    // Create a Seneca instance for testing.
    var seneca = test_seneca(fin)

    // Add dynamic mock messages, just for this test.
    seneca
      .add('follow:list,kind:followers', function (msg, reply) {
        reply(null, ['red', 'green', 'blue'])
      })

    // The final verification step of this test.
      .add('timeline:insert', function (msg, reply) {
        expect(msg.users).to.equal(['red', 'green', 'blue'])
        reply()
        fin()
      })

    // No need to gate this test, as just one message sent.
    seneca

      .act({
        fanout: 'entry',
        user: 'foo',
        text: 't0',
        when: Date.now()
      })
  })
})


// Construct a Seneca instance suitable for unit testing
function test_seneca (fin) {
  return Seneca({log: 'test'})

  // activate unit test mode. Errors provide additional stack tracing context.
  // The fin callback is called when an error occurs anywhere.
    .test(fin)

  // Load the microservice business logic
    .use(require('../fanout-logic'))
}


================================================
FILE: follow/follow-logic.js
================================================
var _ = require('lodash')

module.exports = function follow (options) {
  var seneca = this

  seneca.add('follow:user', function(msg, done) {
    var seneca = this

    relate( seneca, 'followers', msg.target, msg.user, true, function(err) {
      if( err ) return done(err)

      relate( seneca, 'following', msg.user, msg.target, true, function(err) {
        if( err ) return done(err)

        seneca.act('store:list,kind:entry',{user:msg.target}, function(err,list) {
          if( err ) return done(err)

          _.each(list,function(entry){
            seneca.act({
              timeline: 'insert',
              users: [msg.user],
            }, entry.data$())
          })

          done()
        })
      })
    })
  })


  seneca.add('follow:list', function(msg,done){
    this
      .make('follow')
      .load$(msg.user, function(err,follow){
        var list = (follow && follow[msg.kind]) || []
        done(err, list)
      })
  })


  function relate(seneca,relation,from,to,create,done) {
    seneca
      .make('follow')
      .load$(from, function(err, follow) {
        if( err ) return done(err)
        
        if (follow) {
          add_follower( null, follow, done )
        }
        else if (create) {
          this.act('reserve:create', {key: 'follow/'+from}, function (err, status) {
            if( err ) return done(err)
            
            if( !status.ok ) {
              return relate(this,relation,from,to,false,done)
            }

            var follow = this.make('follow',{id$:from})
            follow[relation] = []
            add_follower(err, follow, function (err) {
              if( err ) return done(err)
              
              this.act('reserve:remove', {key: 'follow/'+from})
              done()
            })
          })
        }

        function add_follower( err, follow, done ) {
          if( err ) return done(err)

          follow[relation] = (follow[relation] || [])
          follow[relation].push(to)
          follow[relation] = _.uniq(follow[relation])

          follow.save$(done)
        }
      })
  }
}
 


================================================
FILE: follow/follow-service.js
================================================
var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[3] || '').split(',')
var SILENT = process.env.SILENT || process.argv[4] || 'true'


require('seneca')({
  tag: 'follow',
  internal: {logger: require('seneca-demo-logger')},
  debug: {short_logs: true}
})
    //.use('zipkin-tracer', {sampling:1})
  .use('entity')
  .use('follow-logic')
  .use('mesh',{
    pin: 'follow:*',
    bases: BASES,
    host: HOST,
    sneeze: {
      silent: JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  })

  .ready(function(){
    console.log(this.id)
  })


================================================
FILE: follow/test/follow-test.js
================================================
// Unit test for the follow microservice.
// Uses https://github.com/hapijs/lab but easy to refactor for other unit testers.

// The utility function test_seneca constructs an instance of Seneca
// suitable for test execution, using the seneca.test() method.

var Lab = require('lab')
var Code = require('code')
var Seneca = require('seneca')

var lab = exports.lab = Lab.script()
var describe = lab.describe
var it = lab.it
var expect = Code.expect

// A suite of unit tests for this microservice.
describe('follow', function () {

  // A unit test (the test callback is named 'fin' to distinguish it from others).
  it('add-followers', function (fin) {

    // Create a Seneca instance for testing.
    var seneca = test_seneca(fin)

    seneca.act(
      {
        follow: 'user',
        user: 'f0',
        target: 'u0'
      }, 
      function () {

        seneca
          .act(
            {
              follow: 'user',
              user: 'f1',
              target: 'u0'
            },
            function () {

              seneca
                .act({
                  follow: 'list',
                  user: 'u0',
                  kind: 'followers'
                }, function (ignore, list) {
                  expect(list.length).to.equal(2)
                  expect(list).to.equal(['f0', 'f1'])
                  fin()
                })
            })
      })
  })
})


// Construct a Seneca instance suitable for unit testing
function test_seneca (fin) {
  // In production, reservations will expire
  var reservations = {}

  return Seneca({log: 'test'})

  // activate unit test mode. Errors provide additional stack tracing context.
  // The fin callback is called when a error occurs anywhere.
    .test(fin)

  // Load the plugin dependencies of the microservice
    .use('entity')

  // Load the microservice business logic
    .use(require('../follow-logic'))
  
  // IMPORTANT! Provide mocks for any message patterns that the microservice
  // depends on. In production these are provided by other microservices.
  // To define a mock message, just add an action for the message pattern.

    .add('timeline:insert', function (msg, reply) {
      reply()
    })

    .add('store:list,kind:entry', function (msg, reply) {
      reply(null, [])
    })

    .add('reserve:create', function (msg, reply) {
      if (reservations[msg.key]) {
        return reply(null, {ok: false})
      }
      else {
        reservations[msg.key] = true
        return reply(null, {ok: true})
      }
    })

    .add('reserve:remove', function (msg, reply) {
      reservations[msg.key] = false
    })
}


================================================
FILE: front/front.js
================================================
"use strict"
var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[3] || '').split(',')
var SILENT = process.env.SILENT || process.argv[4] || 'true'


var Hapi = require('hapi')
var Rif = require('rif')

var server = new Hapi.Server()
var rif = Rif()


var host = rif(HOST) || HOST


server.connection({ 
  port: 8000 // test with http://localhost:8000/api/ping
})

server.register(require('inert'))

server.register({
  register: require('wo'),
  options: {
    bases: BASES,
      sneeze: {
	host: host,
	silent: JSON.parse(SILENT),
        swim: {interval: 1111}
      }
  }
})

server.route({ 
  method: 'GET', path: '/api/ping', 
  handler: {
    wo: {}
  }
})

server.route({
  method: 'POST', path: '/api/post/{user}', 
  handler: {
    wo: {
      passThrough: true
    }
  }
})

server.route({
  method: 'POST', path: '/api/follow/{user}', 
  handler: {
    wo: {
      passThrough: true
    }
  }
})


server.route({ 
  method: 'GET', path: '/mine/{user}', 
  handler: {
    wo: {}
  }
})


server.route({ 
  method: ['GET','POST'], path: '/search/{user}', 
  handler: {
    wo: {}
  }
})


server.route({ 
  method: 'GET', path: '/{user}', 
  handler: {
    wo: {}
  }
})

server.route({
  path: '/favicon.ico',
  method: 'get',
  config: {
    cache: {
      expiresIn: 1000*60*60*24*21
    }
  },
  handler: function(request, reply) {
    reply().code(200).type('image/x-icon')
  }
})

server.route({
  method: 'GET',
  path: '/res/{path*}',
  handler: {
    directory: {
      path: __dirname + '/www/res',
    }
  }
})


server.start(function(){
  console.log('front',server.info.uri)
})


================================================
FILE: front/www/res/site.css
================================================
* {
  font-family: arial;
  padding: 0px;
  margin: 0px;
  text-decoration: none;
}

body {
  background-color: #eef;
}

input {
  border: 1px solid #222;
  font-size: 14pt;
  padding: 2px;
}

input[type='text'] {
  width: 25%;
}

div.header {
  background-color: #222;
  margin: 0px 0px 10px 0px;
  padding: 4px;
}

div.header a {
  display: inline-block;
  padding: 4px;
  color: #eee;
}

div.header .nav_active {
  background-color: #eef;
  color: #222;
}

div.container {
  padding: 4px;
}

div.entry {
  padding: 2px;
  margin: 4px 2px;
  border: 1px solid #ccc;
  width: 33%;
  color: #666;
}

div.text {
  color: #222;
  padding: 2px;
  margin-top: 2px;
}


================================================
FILE: fuge/fuge.yml
================================================
fuge_global:
  tail: true
  monitor: false
  auto_generate_environment: false
  monitor_excludes:
    - '**/node_modules/**'
    - '**/.git/**'
    - '**/*.log'
  environment:
    - BASES=127.0.0.1:39000,127.0.0.1:39001
    - HOST=127.0.0.1
    - SILENT=true
base0:
  type: node
  path: ../base
  run: node base.js
  ports:
    - main=39000
  environment:
    - TAG=base0
    - PORT=39000
base1:
  type: node
  path: ../base
  run: node base.js
  ports:
    - main=39001
  environment:
    - TAG=base1
    - PORT=39001
front:
  type: node
  path: ../front
  run: node front.js
  ports:
    - main=8000
  environment:
    - PORT=8000
api:
  type: node
  path: ../api
  run: node api-service.js
  ports:
    - main=8001
  environment:
    - PORT=0
post:
  type: node
  path: ../post
  run: node post-service.js
entry_store:
  type: node
  path: ../entry-store
  run: node entry-store-service.js
entry_cache:
  type: node
  path: ../entry-cache
  run: node entry-cache-service.js
repl:
  type: node
  path: ../repl
  run: node repl-service.js 
  ports:
    - main=10001
  environment:
    - REPL_HOST=127.0.0.1
    - REPL_PORT=10001
mine:
  type: node
  path: ../mine
  run: node mine-service.js
  environment:
    - PORT=0
home:
  type: node
  path: ../home
  run: node home-service.js
  environment:
    - PORT=0
index:
  type: node
  path: ../index
  run: node index-service.js
search:
  type: node
  path: ../search
  run: node search-service.js
  environment:
    - PORT=0
follow:
  type: node
  path: ../follow
  run: node follow-service.js
fanout:
  type: node
  path: ../fanout
  run: node fanout-service.js
timeline0:
  type: node
  path: ../timeline
  run: node timeline-service.js
  environment:
    - SHARD=0
timeline1:
  type: node
  path: ../timeline
  run: node timeline-service.js
  environment:
    - SHARD=1
timeline_shard:
  type: node
  path: ../timeline
  run: node timeline-shard-service.js
reserve:
  type: node
  path: ../reserve
  run: node reserve-service.js



================================================
FILE: home/home-service.js
================================================
"use strict"

var PORT = process.env.PORT || process.argv[2] || 0
var HOST = process.env.HOST || process.argv[3] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[4] || '').split(',')
var SILENT = process.env.SILENT || process.argv[5] || 'true'


var hapi       = require('hapi')
var chairo     = require('chairo')
var vision     = require('vision')
var inert      = require('inert')
var handlebars = require('handlebars')
var _          = require('lodash')
var moment     = require('moment')
var Seneca     = require('seneca')
var Rif        = require('rif')


var tag = 'home'

var server = new hapi.Server()
var rif = Rif()


var host = rif(HOST) || HOST


server.connection({
    port: PORT,
    host: host
})


server.register( vision )
server.register( inert )

server.register({
  register:chairo,
  options:{
    seneca: Seneca({
      tag: tag,
      internal: {logger: require('seneca-demo-logger')},
      debug: {short_logs:true}
    })
      //.use('zipkin-tracer', {sampling:1})
  }
})

server.register({
  register: require('wo'),
  options:{
    bases: BASES,
    route: [
        {path: '/{user}'},
    ],
    sneeze: {
      host: host,
      silent: JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  }
})


server.views({
  engines: { html: handlebars },
  path: __dirname + '/www',
  layout: true
})


server.route({
  method: 'GET', path: '/{user}',
  handler: function( req, reply )
  {
    server.seneca.act(
      'timeline:list',
      {user:req.params.user},
      function( err, entrylist ) {
        if(err) {
          entrylist = []
        }

        reply.view('home',{
          user: req.params.user,
          entrylist: _.map(entrylist,function(entry){
            entry.when = moment(entry.when).fromNow()
            return entry
          })
        })
      })
  }
})


server.seneca.use('mesh',{
    host:host,
    bases:BASES,
    sneeze: {
      silent: JSON.parse(SILENT),
      swim: {interval: 1111}
    }
})


server.start(function(){
  console.log(tag,server.info.host,server.info.port)
})



================================================
FILE: home/www/home.html
================================================
<form action="/api/post/{{user}}" method="post">
<input type="text" name="text">
<input type="hidden" name="from" value="/{{user}}">
<input type="submit" value="post">
</form>

<br>

{{#each entrylist}}
<div class="entry">
<form action="/api/follow/{{../user}}" method="post">
<b><a href="/{{this.user}}">@{{this.user}}</a></b> <small>{{this.when}}</small>
<input type="hidden" name="from" value="/{{../user}}">
<input type="hidden" name="user" value="{{this.user}}">
{{#if can_follow}}
<input type="submit" value="follow">
{{/if}}
<br>
<div class="text">
{{this.text}}
</div>
</form>
</div>
{{/each}}




================================================
FILE: home/www/layout.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Microblog</title>
  <link rel="stylesheet" href="/res/site.css">
</head>
<body>

  <div class="header">
    <a href="/{{user}}" class="nav_active">Home</a>
    <a href="/mine/{{user}}">Mine</a> 
    <a href="/search/{{user}}">Search</a>
  </div>

  <div class="container">
    {{{ content }}}
  </div>

</body>
</html>



================================================
FILE: index/index-logic.js
================================================
// Business logic for the index microservice.
// Provides a full text search index for microblog entries.


// Modules providing a simple in-memory full text search index.
// In production you could replace these with API calls to elasticsearch
// or similar.
var Levelup = require('levelup')
var Memdown = require('memdown')
var Search  = require('search-index')


// This is the standard way to define a Seneca plugin.
module.exports = function index (options) {

  // The plugin Seneca instance is provided by `this`.
  // This Seneca instance tracks patterns against this plugin
  // as an aid to debugging.
  var seneca = this


  // The search index. This is the internal state of the service. In general.
  // services should *not* have internal state, as it has to be synchronized
  // between multiple instances. This service is purely for demonstration purposes,
  // and only a single instance should be run.
  var index


  // The Seneca patterns that this plugin defines.
  // This is the `interface` for this plugin - matching messages will end up here.
  seneca.add('search:query', search_query)
  seneca.add('search:insert', search_insert)
  seneca.add('init:index', init)


  // Query the search index.
  // The implementation logic consists of calls to the search index API.
  function search_query (msg, done) {
    console.log(terms)

    var terms = msg.query.split(/ +/)

    var query = {
      query: {
        AND: {text:terms}
      }
    }

    index.search(query, function (err, out) {
      var hits = (out && out.hits) || []

      hits = hits.map(function (hit) {
        return hit.document
      })

      done(null, hits)
    })
  }


  // Insert a document into the search index.
  function search_insert (msg, done) {
    index.add([{
      id: msg.id,
      text: msg.text,
      user: msg.user,
      when: msg.when
    }], {}, done)
  }


  // Initialize the plugin. This is the standard mechanism to initialize a Seneca
  // plugin - by defining a special pattern of the form init:<plugin-name>.
  function init (msg, done) {
    Search({
      indexes: Levelup('si', {
        db: Memdown, 
        valueEncoding: 'json'
      })
    }, function(err, si) {
      if (err) return done(err)
      index = si
      done()
    })
  }
}


================================================
FILE: index/index-service.js
================================================
var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[3] || '').split(',')
var SILENT = process.env.SILENT || process.argv[4] || 'true'


require('seneca')({
  tag: 'index',
  internal: {logger: require('seneca-demo-logger')},
  debug: {short_logs: true}
})
    //.use('zipkin-tracer', {sampling:1})

  .use('index-logic')

  .add('info:entry', function(msg,done){
    delete msg.info
    this.act('search:insert',msg,done)
  })

  .use('mesh',{
    listen:[
      {pin: 'search:*'},
      {pin: 'info:entry', model:'observe'}
    ],
    bases: BASES,
    host: HOST,
    sneeze: {
      silent: JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  })

  .ready(function(){
    console.log(this.id)
  })


================================================
FILE: index/test/index-test.js
================================================
// Unit test for the index microservice.
// Uses https://github.com/hapijs/lab but easy to refactor for other unit testers.

// The utility function test_seneca constructs an instance of Seneca
// suitable for test execution, using the seneca.test() method.

var Lab = require('lab')
var Code = require('code')
var Seneca = require('seneca')

var lab = exports.lab = Lab.script()
var describe = lab.describe
var it = lab.it
var expect = Code.expect

// A suite of unit tests for this microservice.
describe('index', function () {

  // A unit test (the test callback is named 'fin' to distinguish it from others).
  it('insert-query', function (fin) {

    // Create a Seneca instance for testing.
    var seneca = test_seneca(fin)

    // Gate the execution of actions for this instance. Gated actions are executed
    // in sequence and each action waits for the previous one to complete. Gating
    // is not required, but avoids excessive callbacks in the unit test code.
    seneca
      .gate()

    // Send an action, and validate the response.
      .act({
        search: 'insert',
        id: ''+Math.random(),
        when: Date.now(),
        user: 'u0',
        text: 'lorem ipsum dolor sit amet'
      }, function (ignore) {})

      .act({
        search: 'query',
        query: 'ipsum',

        // Because test mode is active, it is not necessary to handle
        // callback errors. These are passed directly to the 'fin' callback.
      }, function (ignore, list) {
        expect(list.length).to.equal(1)
        expect(list[0].text).to.equal('lorem ipsum dolor sit amet')
      })

    // Once all the tests are complete, invoke the test callback
      .ready(fin)
  })
})


// Construct a Seneca instance suitable for unit testing
function test_seneca (fin) {
  return Seneca({log: 'test'})

  // activate unit test mode. Errors provide additional stack tracing context.
  // The fin callback is called when an error occurs anywhere.
    .test(fin)

  // Load the microservice business logic.
    .use(require('../index-logic'))
}


================================================
FILE: mine/mine-service.js
================================================
"use strict"

var PORT = process.env.PORT || process.argv[2] || 0
var HOST = process.env.HOST || process.argv[3] || 0
var BASES = (process.env.BASES || process.argv[4] || '').split(',')
var SILENT = process.env.SILENT || process.argv[5] || 'true'

var hapi       = require('hapi')
var chairo     = require('chairo')
var vision     = require('vision')
var inert      = require('inert')
var handlebars = require('handlebars')
var _          = require('lodash')
var moment     = require('moment')
var Seneca     = require('seneca')
var Rif        = require('rif')


var server = new hapi.Server()
var rif = Rif()


var host = rif(HOST) || HOST


server.connection({
    port: PORT,
    host: host
})

server.register( vision )
server.register( inert )

server.register({
  register:chairo,
  options:{
    seneca: Seneca({
      tag: 'mine',
      internal: {logger: require('seneca-demo-logger')},
      debug: {short_logs:true}
    })
      //.use('zipkin-tracer', {sampling:1})
      .use('entity')
  }
})

server.register({
  register: require('wo'),
  options:{
    bases: BASES,
    route: [
        {path: '/mine/{user}'},
    ],
    sneeze: {
      host: host,
      silent: JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  }
})


server.views({
  engines: { html: handlebars },
  path: __dirname + '/www',
  layout: true
})


server.route({
  method: 'GET', path: '/mine/{user}',
  handler: function( req, reply )
  {
    server.seneca.act(
      'store:list,kind:entry',
      {user:req.params.user},
      function( err, entrylist ) {
        if(err) {
          entrylist = []
        }

        reply.view('mine',{
          user: req.params.user,
          entrylist: _.map(entrylist,function(entry){
            entry.when = moment(entry.when).fromNow()
            return entry
          })
        })
      })
  }
})


server.seneca.use('mesh',{
    bases:BASES,
    host:host
})

server.start(function(){
  console.log('mine',server.info.host,server.info.port)
})




================================================
FILE: mine/www/layout.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Microblog</title>
  <link rel="stylesheet" href="/res/site.css">
</head>
<body>

  <div class="header">
    <a href="/{{user}}">Home</a>
    <a href="/mine/{{user}}" class="nav_active">Mine</a> 
    <a href="/search/{{user}}">Search</a>
  </div>

  <div class="container">
    {{{ content }}}
  </div>

</body>
</html>



================================================
FILE: mine/www/mine.html
================================================
<form action="/api/post/{{user}}" method="post">
<input type="text" name="text">
<input type="hidden" name="from" value="/mine/{{user}}">
<input type="submit" value="post">
</form>

<br>

{{#each entrylist}}
<div class="entry">
<small>{{this.when}}</small>
<br>
<div class="text">
{{this.text}}
</div>
</form>
</div>
{{/each}}




================================================
FILE: monitor/monitor.js
================================================
var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[3] || '127.0.0.1:39000,127.0.0.1:39001').split(',')

require('seneca')()//({log: 'silent'})
  .use('mesh',{
    bases: BASES,
    host: HOST,
    monitor: true,
    tag: null
  })


================================================
FILE: package.json
================================================
{
  "name": "ramanujan",
  "version": "0.0.1",
  "description": "ramanujan",
  "main": "ramanujan.js",
  "scripts": {
    "test": "lab -v */test -I nil"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/senecajs/ramanujan.git"
  },
  "keywords": [
    "seneca",
    "demo",
    "ramanujan"
  ],
  "author": "Richard Rodger richardrodger.com",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/senecajs/ramanujan/issues"
  },
  "homepage": "https://github.com/senecajs/ramanujan",
  "dependencies": {
    "chairo": "2.2.1",
    "handlebars": "4.0.5",
    "hapi": "15.0.3",
    "inert": "4.0.2",
    "levelup": "1.3.3",
    "lodash": "4.15.0",
    "memdown": "1.2.0",
    "moment": "2.14.1",
    "search-index": "0.8.15",
    "seneca": "3.3.0",
    "seneca-balance-client": "0.6.0",
    "seneca-basic": "0.5.0",
    "seneca-demo-logger": "0.2.0",
    "seneca-entity": "1.3.0",
    "seneca-mesh": "0.10.0",
    "seneca-repl": "0.3.0",
    "seneca-zipkin-tracer": "0.1.0",
    "sneeze": "https://github.com/rjrodger/sneeze#diagnostic",
    "vision": "4.1.0",
    "wo": "0.8.0",
    "rif": "0.3.0"
  },
  "devDependencies": {
    "code": "3.0.2",
    "lab": "11.0.1"
  }
}


================================================
FILE: post/post-logic.js
================================================
module.exports = function post (options) {
  var seneca = this

  seneca.add('post:entry', function(msg, done) {
    var entry = this.util.clean(msg)
    delete entry.post

    entry.when = Date.now()

    this.act('store:save,kind:entry', entry, function(err,entry) {
	done(err)

      if( !err ) {
        this.act('info:entry',entry.data$())
      }
    })
  })
}


================================================
FILE: post/post-service.js
================================================
var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[3] || '').split(',')
var SILENT = process.env.SILENT || process.argv[4] || 'true'


require('seneca')({
  tag: 'post',
  internal: {logger: require('seneca-demo-logger')},
  debug: { short_logs: true }
})
  //.use('zipkin-tracer', {sampling:1})
  .use('entity')
  .use('post-logic')

  .use('mesh',{
    pin: 'post:*',
    bases: BASES,
    host: HOST,
    sneeze: {
      silent: JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  })

  .ready(function(){
    console.log(this.id)
  })


================================================
FILE: post/test/post-test.js
================================================
// Unit test for the post microservice.
// Uses https://github.com/hapijs/lab but easy to refactor for other unit testers.

// The utility function test_seneca constructs an instance of Seneca
// suitable for test execution, using the seneca.test() method.

var Lab = require('lab')
var Code = require('code')
var Seneca = require('seneca')

var lab = exports.lab = Lab.script()
var describe = lab.describe
var it = lab.it
var expect = Code.expect

// A suite of unit tests for this microservice.
describe('post', function () {

  // A unit test (the test callback is named 'fin' to distinguish it from others).
  it('entry', function (fin) {

    // Create a Seneca instance for testing.
    var seneca = test_seneca(fin)

    // Add dynamic mock messages, just for this test.
    seneca
      .add('store:save,kind:entry', function (msg, reply) {
        reply(null, this.make('entry', {
          when: msg.when,
          user: msg.user,
          text: msg.text
        }))
      })

    // The final verification step of this test.
      .add('info:entry', function (msg, reply) {
        expect(msg.user).to.equal('u0')
        expect(msg.text).to.equal('t0')
        reply()
        fin()
      })

    // No need to gate this test, as just one message sent.
    seneca

      .act({
        post: 'entry',
        user: 'u0',
        text: 't0',
        when: Date.now()
      })
  })
})


// Construct a Seneca instance suitable for unit testing
function test_seneca (fin) {
  return Seneca({log: 'test'})

  // activate unit test mode. Errors provide additional stack tracing context.
  // The fin callback is called when an error occurs anywhere.
    .test(fin)

  // The test needs to construct entities
    .use('entity')

  // Load the microservice business logic
    .use(require('../post-logic'))
}


================================================
FILE: repl/repl-service.js
================================================
var REPL_PORT = parseInt(process.env.REPL_PORT || process.argv[2] || 10001)
var REPL_HOST = process.env.REPL_HOST || process.argv[3] || '127.0.0.1'
var HOST = process.env.HOST || process.argv[4] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[5] || '').split(',')
var SILENT = process.env.SILENT || process.argv[6] || 'true'


var repl = require('seneca-repl');


var seneca = require('seneca')({
  tag: 'repl',
  internal: {logger: require('seneca-demo-logger')},
  debug: {short_logs:true}
})
//.use('zipkin-tracer', {sampling:1})
.use('mesh',{
  tag: null, // ensures membership of all tagged meshes
  bases: BASES,
  host: HOST,
  make_entry: function( entry ) {
    if( 'wo' === entry.tag$ ) {
      return {
        route: JSON.stringify(entry.route),
        host: entry.host,
        port: entry.port,
        identifier: entry.identifier$
      }
    }
  },
  sneeze:{
    silent: JSON.parse(SILENT),
    swim: {interval: 1111}
  }
})
.use(repl)
.ready(function () {
  seneca.repl({
    port: REPL_PORT,
    host: REPL_HOST,
      alias: {
      m: 'role:mesh,get:members'
    }
  })
})


================================================
FILE: reserve/reserve-logic.js
================================================
var _ = require('lodash')

module.exports = function follow (options) {
  var seneca = this

  var interval = options.interval || 22
  var expires  = options.expires || 1111


  seneca.add('reserve:create', reserve_create)
  seneca.add('reserve:read', reserve_read)
  seneca.add('reserve:remove', reserve_remove)
  seneca.add('reserve:state', reserve_state)


  var reservations = {}
  
  
  setInterval(function () {
    var now = Date.now()
    Object.keys(reservations).forEach(function (key) {
      var when = reservations[key]

      if (expires < now - when) {
        delete reservations[key]
      }
    })
  }, interval)


  function reserve_create(msg, reply) {
    var key = msg.key
    
    if (reservations[key]) {
      return reply(null, {ok:false})
    }

    reservations[key] = Date.now()
    return reply(null, {ok:true})
  }


  function reserve_read(msg, reply) {
    return reply(null, {ok: !!reservations[msg.key]})
  }


  function reserve_remove(msg, reply) {
    var found = !!reservations[msg.key]
    delete reservations[msg.key]
    return reply(null, {ok:found})
  }


  function reserve_state(msg, reply) {
    return reply(null, reservations)
  }
}
 


================================================
FILE: reserve/reserve-service.js
================================================
var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[3] || '').split(',')
var SILENT = process.env.SILENT || process.argv[4] || 'true'

require('seneca')({
  tag: 'reserve',
  internal: {logger: require('seneca-demo-logger')},
  debug: {short_logs: true}
})
  //.use('zipkin-tracer', {sampling:1})
  .use('reserve-logic')

  .use('mesh',{
    pin: 'reserve:*',
    bases: BASES,
    host: HOST,
    sneeze: {
      silent:JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  })

  .ready(function(){
    console.log(this.id)
  })


================================================
FILE: reserve/test/reserve-test.js
================================================
// Unit test for the reserve microservice.
// Uses https://github.com/hapijs/lab but easy to refactor for other unit testers.

// The utility function test_seneca constructs an instance of Seneca
// suitable for test execution, using the seneca.test() method.

var Lab = require('lab')
var Code = require('code')
var Seneca = require('seneca')

var lab = exports.lab = Lab.script()
var describe = lab.describe
var it = lab.it
var expect = Code.expect

// A suite of unit tests for this microservice.
describe('reserve', function () {

  // A unit test (the test callback is named 'fin' to distinguish it from others).
  it('create-remove', function (fin) {

    // Create a Seneca instance for testing.
    var seneca = test_seneca(fin)

    // Gate the execution of actions for this instance. Gated actions are executed
    // in sequence and each action waits for the previous one to complete. Gating
    // is not required, but avoids excessive callbacks in the unit test code.
    seneca
      .gate()

    // Send an action, and validate the response.
      .act({
        reserve: 'create',
        key: 'k0'
      }, function (ignore, status) {
        expect(status.ok).to.equal(true)
      })

      .act({
        reserve: 'create',
        key: 'k1'
      }, function (ignore, status) {
        expect(status.ok).to.equal(true)
      })


      .act({
        reserve: 'create',
        key: 'k0'
      }, function (ignore, status) {
        expect(status.ok).to.equal(false)
      })

      .act({
        reserve: 'create',
        key: 'k1'
      }, function (ignore, status) {
        expect(status.ok).to.equal(false)
      })


      .act({
        reserve: 'remove',
        key: 'k1'
      })


      .act({
        reserve: 'create',
        key: 'k0'
      }, function (ignore, status) {
        expect(status.ok).to.equal(false)
      })

      .act({
        reserve: 'create',
        key: 'k1'
      }, function (ignore, status) {
        expect(status.ok).to.equal(true)
      })


      .act({
        reserve: 'remove',
        key: 'k0'
      })

      .act({
        reserve: 'remove',
        key: 'k1'
      })


      .act({
        reserve: 'create',
        key: 'k0'
      }, function (ignore, status) {
        expect(status.ok).to.equal(true)
      })

      .act({
        reserve: 'create',
        key: 'k1'
      }, function (ignore, status) {
        expect(status.ok).to.equal(true)
      })

    setTimeout(function() {
      seneca

        .act({
          reserve: 'read',
          key: 'k0'
        }, function (ignore, status) {
          expect(status.ok).to.equal(false)
        })

        .act({
          reserve: 'read',
          key: 'k1'
        }, function (ignore, status) {
          expect(status.ok).to.equal(false)
        })

      fin()
    }, 222)
  })
})


// Construct a Seneca instance suitable for unit testing
function test_seneca (fin) {
  return Seneca({log: 'test'})

  // activate unit test mode. Errors provide additional stack tracing context.
  // The fin callback is called when a error occurs anywhere.
    .test(fin)

  // Load the microservice business logic
    .use(require('../reserve-logic'), {interval: 11, expires: 111})
}


================================================
FILE: search/search-service.js
================================================
"use strict"

var PORT = process.env.PORT || process.argv[2] || 0
var HOST = process.env.HOST || process.argv[3] || 0
var BASES = (process.env.BASES || process.argv[4] || '').split(',')
var SILENT = process.env.SILENT || process.argv[5] || 'true'


var hapi       = require('hapi')
var chairo     = require('chairo')
var vision     = require('vision')
var inert      = require('inert')
var handlebars = require('handlebars')
var _          = require('lodash')
var moment     = require('moment')
var Seneca     = require('seneca')
var Rif        = require('rif')


var server = new hapi.Server()
var rif = Rif()


var host = rif(HOST) || HOST


server.connection({
    port: PORT,
    host: host
})


server.register( vision )
server.register( inert )

server.register({
  register:chairo,
  options:{
    seneca: Seneca({
      tag: 'search',
      internal: {logger: require('seneca-demo-logger')},
      debug: {short_logs:true}
    })
	  //.use('zipkin-tracer', {sampling:1})
  }
})

server.register({
  register: require('wo'),
  options:{
    bases: BASES,
    route: [
        {method: ['GET','POST'], path: '/search/{user}'},
    ],
    sneeze: {
      host: host,
      silent: JSON.parse(SILENT)
    }
  }
})


server.views({
  engines: { html: handlebars },
  path: __dirname + '/www',
  layout: true
})


server.route({
  method: ['GET','POST'],
  path: '/search/{user}',
  handler: function( req, reply )
  {
    var query
      = (req.query ? (null == req.query.query ? '' : ' '+req.query.query) : '')
      + (req.payload ? (null == req.payload.query ? '' : ' '+req.payload.query) : '')

    query = query.replace(/^ +/,'')
    query = query.replace(/ +$/,'')

    server.seneca.act(
      'follow:list,kind:following',
      {user:req.params.user},
      function(err,following){
        if( err ) {
          following = []
        }

        this.act(
          'search:query',
          {query: query },
          function( err, entrylist ) {
            if(err) {
              this.log.warn(err)
              entrylist = []
            }

            reply.view('search',{
              query: encodeURIComponent(query),
              user: req.params.user,
              entrylist: _.map(entrylist,function(entry){
                entry.when = moment(entry.when).fromNow()
                entry.can_follow =
                  req.params.user != entry.user &&
                  !_.includes(following,entry.user)
                return entry
              })
            })
          })
      })
  }
})


server.seneca.use('mesh',{
  bases:BASES,
  host:host,
  sneeze:{
    silent: JSON.parse(SILENT),
    swim: {interval: 1111}
  }
})

server.start(function(){
  console.log('search',server.info.uri)
})



================================================
FILE: search/www/layout.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Microblog</title>
  <link rel="stylesheet" href="/res/site.css">
</head>
<body>

  <div class="header">
    <a href="/{{user}}">Home</a>
    <a href="/mine/{{user}}">Mine</a> 
    <a href="/search/{{user}}" class="nav_active">Search</a>
  </div>

  <div class="container">
    {{{ content }}}
  </div>

</body>
</html>



================================================
FILE: search/www/search.html
================================================
<form action="/search/{{user}}" method="post">
<input type="text" name="query">
<input type="submit" value="search">
</form>

<br>

{{#each entrylist}}
<div class="entry">
<form action="/api/follow/{{../user}}" method="post">
<b><a href="/{{this.user}}">@{{this.user}}</a></b> <small>{{this.when}}</small>
<input type="hidden" name="from" value="/search/{{../user}}?query={{{../query}}}">
<input type="hidden" name="user" value="{{this.user}}">
{{#if can_follow}}
<input type="submit" value="follow">
{{/if}}
<br>
<div class="text">
{{this.text}}
</div>
</form>
</div>
{{/each}}




================================================
FILE: start.sh
================================================
HOST="127.0.0.1"
BASES="127.0.0.1:39000,127.0.0.1:39001"
OPTS=""

# for demos use OPTS = '--seneca.options.debug.undead=true --seneca.options.plugin.mesh.sneeze.silent=1'


node base/base.js base0 39000 $HOST $BASES $OPTS &
sleep 1
node base/base.js base1 39001 $HOST $BASES $OPTS &
sleep 1
node front/front.js $HOST $BASES $OPTS &
sleep 1
node api/api-service.js 0 $HOST $BASES $OPTS &
sleep 1
node post/post-service.js $HOST $BASES $OPTS &
sleep 1
node entry-store/entry-store-service.js $HOST $BASES $OPTS &
sleep 1
node entry-cache/entry-cache-service.js $HOST $BASES $OPTS &
sleep 1
node repl/repl-service.js 10001 $HOST $HOST $BASES $OPTS &
sleep 1
node mine/mine-service.js 0 $HOST $BASES $OPTS &
sleep 1
node home/home-service.js 0 $HOST $BASES $OPTS &
sleep 1
node search/search-service.js 0 $HOST $BASES $OPTS &
sleep 1
node index/index-service.js $HOST $BASES $OPTS &
sleep 1
node follow/follow-service.js $HOST $BASES $OPTS &
sleep 1
node fanout/fanout-service.js $HOST $BASES $OPTS &
sleep 1
node timeline/timeline-service.js 0 $HOST $BASES $OPTS &
sleep 1
node timeline/timeline-service.js 1 $HOST $BASES $OPTS &
sleep 1
node timeline/timeline-shard-service.js $HOST $BASES $OPTS &
sleep 1
node reserve/reserve-service.js $HOST $BASES $OPTS &







================================================
FILE: timeline/test/timeline-test.js
================================================
// Unit test for the timeline microservice.
// Uses https://github.com/hapijs/lab but easy to refactor for other unit testers.

// The utility function test_seneca constructs an instance of Seneca
// suitable for test execution, using the seneca.test() method.

var Lab = require('lab')
var Code = require('code')
var Seneca = require('seneca')

var lab = exports.lab = Lab.script()
var describe = lab.describe
var it = lab.it
var expect = Code.expect

// A suite of unit tests for this microservice.
describe('timeline', function () {

  // A unit test (the test callback is named 'fin' to distinguish it from others).
  it('insert-list', function (fin) {

    // Create a Seneca instance for testing.
    var seneca = test_seneca(fin)

    // Gate the execution of actions for this instance. Gated actions are executed
    // in sequence and each action waits for the previous one to complete. Gating
    // is not required, but avoids excessive callbacks in the unit test code.
    seneca
      .gate()

    // Send an action; there's no response expected for this message.
    // User aaa has posted entry t0 which is inserted into the timelines of
    // users bbb and ccc.
      .act({
        timeline: 'insert',
        user: 'aaa',
        users: ['bbb', 'ccc'],
        text: 't0',
        when: Date.now()
      })

      .act({
        timeline: 'insert',
        user: 'bbb',
        users: ['aaa', 'ccc'],
        text: 't1',
        when: Date.now()
      })

    // aaa has the t1 entry by bbb
      .act({timeline: 'list', user: 'aaa'},
           function (ignore, list) {
             expect(list.length).to.equal(1)
             expect(list[0].text).to.equal('t1')
           })

    // bbb has the to entry by aaa
      .act({timeline: 'list', user: 'bbb'},
           function (ignore, list) {
             expect(list.length).to.equal(1)
             expect(list[0].text).to.equal('t0')
           })
    
    // ccc has nothing
      .act({timeline: 'list', user: 'ccc'},
           function (ignore, list) {
             expect(list.length).to.equal(0)
           })
    
    // Once all the tests are complete, invoke the test callback
      .ready(fin)
  })
})


// Construct a Seneca instance suitable for unit testing
function test_seneca (fin) {
  // In production, reservations will expire
  var reservations = {}

  return Seneca({log: 'test'})

  // activate unit test mode. Errors provide additional stack tracing context.
  // The fin callback is called when a error occurs anywhere.
    .test(fin)

  // Load the plugin dependencies of the microservice
    .use('basic')
    .use('entity')

  // Load the microservice business logic
    .use(require('../timeline-logic'))
  
  // IMPORTANT! Provide mocks for any message patterns that the microservice
  // depends on. In production these are provided by other microservices.
  // To define a mock message, just add an action for the message pattern.

    .add('follow:list,kind:following', function (msg, reply) {
      reply(null, ['bbb'])
    })

    .add('reserve:create', function (msg, reply) {
      if (reservations[msg.key]) {
        return reply(null, {ok: false})
      }
      else {
        reservations[msg.key] = true
        return reply(null, {ok: true})
      }
    })

    .add('reserve:remove', function (msg, reply) {
      reservations[msg.key] = false
    })
}


================================================
FILE: timeline/timeline-logic.js
================================================
'use strict'

var _ = require('lodash')

module.exports = function timeline (options) {
  var seneca = this


  seneca.add('timeline:insert', insert)
  seneca.add('timeline:list', list)


  function insert (msg, done) {
    var seneca = this
    done()

    var entry = {
      user: msg.user,
      text: msg.text,
      when: msg.when,
    }

    var users = _.clone(msg.users)
    var index = -1

    do_user()

    function do_user(err) {
      // try to complete the entire list, despite individual errors
      if( err ) {
        seneca.log.error(err)
      }

      ++index
      if( users.length <= index ) return

      insert_entry( users[index], true, do_user )
    }


    function insert_entry( user, create, next ) {
      seneca
        .make('timeline')
        .load$(user,function(err,timeline){
          if(err) return next(err)

          if (timeline) {
            do_insert(timeline, next)
          }
          else if (create) {
            this.act(
              'reserve:create', 
              {key: 'timeline/'+user}, 
              function (err, status) {
                if( err ) return next(err)
            
                if( !status.ok ) {
                  return insert_entry(user, false, next)
                }

                this
                  .make('timeline',{id$:user, entrylist:[]})
                  .save$( function(err,timeline) {
                    if( err ) return next(err)

                    do_insert(timeline, function (err) {
                      if( err ) return next(err)

                      this.act('reserve:remove', {key: 'timeline/'+user})
                    })
                  })
              })
          }

          function do_insert (timeline, next) {
            timeline.entrylist.push(entry)
            timeline.entrylist.sort(function(a,b){
              return b.when - a.when
            })

            timeline.save$(next)
          }
        })
    }
  }


  function list (msg, done) {
      this.act('follow:list,kind:following',{user:msg.user,default$:{}},function(err,following){
      if( err ) return done(err)

      this
        .make('timeline')
        .load$(msg.user,function(err,timeline){
          var entrylist = (timeline ? timeline.entrylist : [])
          _.each(entrylist,function(entry){
            entry.can_follow = 
              entry.user !== msg.user && 
              !_.includes(following,entry.user)
          })

          done(err,entrylist)
        })
    })
  }
}


================================================
FILE: timeline/timeline-service.js
================================================
var SHARD = process.env.SHARD || process.argv[2] || 0
var HOST = process.env.HOST || process.argv[3] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[4] || '').split(',')
var SILENT = process.env.SILENT || process.argv[5] || 'true'

require('seneca')({
  tag: 'timeline'+SHARD,
  internal: {logger: require('seneca-demo-logger')},
  debug: {short_logs:true}
})
  .use('zipkin-tracer', {sampling:1})
  .use('entity')
  .use('timeline-logic')
  .use('mesh',{
    pin: 'timeline:*,shard:'+SHARD,
    bases: BASES,
    host: HOST,
    sneeze: {
      silent:JSON.parse(SILENT),
      swim: {interval: 1111}
    }
  })

  .ready(function(){
    console.log(this.id)
  })


================================================
FILE: timeline/timeline-shard-service.js
================================================
var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'
var BASES = (process.env.BASES || process.argv[3] || '').split(',')
var SILENT = process.env.SILENT || process.argv[5] || 'true'


var _ = require('lodash')

function resolve_shard(user) {
  return user.charCodeAt(0) % 2
}

require('seneca')({
  tag: 'timeline-shard',
  internal: {logger: require('seneca-demo-logger')},
  debug: {short_logs:true}
})
    //.use('zipkin-tracer', {sampling:1})

  .add('timeline:list',function(msg,done){
    var shard = resolve_shard(msg.user)
    this.act({shard:shard},msg,done)
  })

  .add('timeline:insert',function(msg,done){
    var seneca = this
    done()

    var shards = [[],[]]

    _.each(msg.users,function(user){
      shards[resolve_shard(user)].push(user)
    })

    _.each(shards,function(users,shard){
      if( 0 < users.length ) {
        seneca.act({
          shard: shard,
          users: users,
        }, msg)
      }
    })
  })

  .use('mesh',{
    pin: 'timeline:*',
      bases: BASES,
      host: HOST,
      sneeze:{
        silent: JSON.parse(SILENT),
        swim: {interval: 1111}
      }
  })

  .ready(function(){
    console.log(this.id)
  })
Download .txt
gitextract_lorkntwj/

├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── api/
│   └── api-service.js
├── base/
│   └── base.js
├── docker/
│   ├── Makefile
│   ├── api/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   └── api-service.js
│   ├── base/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   └── base.js
│   ├── docker.txt
│   ├── entry-cache/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── entry-cache-logic.js
│   │   └── entry-cache-service.js
│   ├── entry-store/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── entry-store-logic.js
│   │   └── entry-store-service.js
│   ├── fanout/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── fanout-logic.js
│   │   └── fanout-service.js
│   ├── follow/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── follow-logic.js
│   │   └── follow-service.js
│   ├── front/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── front.js
│   │   └── www/
│   │       └── res/
│   │           └── site.css
│   ├── home/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── home-service.js
│   │   └── www/
│   │       ├── home.html
│   │       └── layout.html
│   ├── index/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── index-logic.js
│   │   └── index-service.js
│   ├── mine/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── mine-service.js
│   │   └── www/
│   │       ├── home.html
│   │       ├── layout.html
│   │       └── mine.html
│   ├── post/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── post-logic.js
│   │   └── post-service.js
│   ├── ramanujan.yml
│   ├── repl/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── monitor.js
│   │   └── repl-service.js
│   ├── reserve/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── reserve-logic.js
│   │   └── reserve-service.js
│   ├── search/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── search-service.js
│   │   └── www/
│   │       ├── home.html
│   │       ├── layout.html
│   │       └── search.html
│   ├── shared/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   └── package.json
│   ├── timeline/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   └── timeline-shard-service.js
│   └── timeline-shard/
│       ├── Dockerfile
│       ├── Makefile
│       ├── timeline-logic.js
│       └── timeline-service.js
├── entry-cache/
│   ├── entry-cache-logic.js
│   ├── entry-cache-service.js
│   └── test/
│       └── entry-cache-test.js
├── entry-store/
│   ├── entry-store-logic.js
│   ├── entry-store-service.js
│   └── test/
│       └── entry-store-test.js
├── fanout/
│   ├── fanout-logic.js
│   ├── fanout-service.js
│   └── test/
│       └── fanout-test.js
├── follow/
│   ├── follow-logic.js
│   ├── follow-service.js
│   └── test/
│       └── follow-test.js
├── front/
│   ├── front.js
│   └── www/
│       └── res/
│           └── site.css
├── fuge/
│   └── fuge.yml
├── home/
│   ├── home-service.js
│   └── www/
│       ├── home.html
│       └── layout.html
├── index/
│   ├── index-logic.js
│   ├── index-service.js
│   └── test/
│       └── index-test.js
├── mine/
│   ├── mine-service.js
│   └── www/
│       ├── layout.html
│       └── mine.html
├── monitor/
│   └── monitor.js
├── package.json
├── post/
│   ├── post-logic.js
│   ├── post-service.js
│   └── test/
│       └── post-test.js
├── repl/
│   └── repl-service.js
├── reserve/
│   ├── reserve-logic.js
│   ├── reserve-service.js
│   └── test/
│       └── reserve-test.js
├── search/
│   ├── search-service.js
│   └── www/
│       ├── layout.html
│       └── search.html
├── start.sh
└── timeline/
    ├── test/
    │   └── timeline-test.js
    ├── timeline-logic.js
    ├── timeline-service.js
    └── timeline-shard-service.js
Download .txt
SYMBOL INDEX (30 symbols across 18 files)

FILE: docker/follow/follow-logic.js
  function relate (line 42) | function relate(seneca,relation,from,to,create,done) {

FILE: docker/index/index-logic.js
  function search_query (line 38) | function search_query (msg, done) {
  function search_insert (line 62) | function search_insert (msg, done) {
  function init (line 74) | function init (msg, done) {

FILE: docker/reserve/reserve-logic.js
  function reserve_create (line 31) | function reserve_create(msg, reply) {
  function reserve_read (line 43) | function reserve_read(msg, reply) {
  function reserve_remove (line 48) | function reserve_remove(msg, reply) {
  function reserve_state (line 55) | function reserve_state(msg, reply) {

FILE: docker/timeline-shard/timeline-logic.js
  function insert (line 13) | function insert (msg, done) {
  function list (line 88) | function list (msg, done) {

FILE: docker/timeline/timeline-shard-service.js
  function resolve_shard (line 8) | function resolve_shard(user) {

FILE: entry-cache/test/entry-cache-test.js
  function test_seneca (line 145) | function test_seneca (fin) {

FILE: entry-store/test/entry-store-test.js
  function test_seneca (line 74) | function test_seneca (fin) {

FILE: fanout/test/fanout-test.js
  function test_seneca (line 52) | function test_seneca (fin) {

FILE: follow/follow-logic.js
  function relate (line 42) | function relate(seneca,relation,from,to,create,done) {

FILE: follow/test/follow-test.js
  function test_seneca (line 59) | function test_seneca (fin) {

FILE: index/index-logic.js
  function search_query (line 38) | function search_query (msg, done) {
  function search_insert (line 62) | function search_insert (msg, done) {
  function init (line 74) | function init (msg, done) {

FILE: index/test/index-test.js
  function test_seneca (line 58) | function test_seneca (fin) {

FILE: post/test/post-test.js
  function test_seneca (line 57) | function test_seneca (fin) {

FILE: reserve/reserve-logic.js
  function reserve_create (line 31) | function reserve_create(msg, reply) {
  function reserve_read (line 43) | function reserve_read(msg, reply) {
  function reserve_remove (line 48) | function reserve_remove(msg, reply) {
  function reserve_state (line 55) | function reserve_state(msg, reply) {

FILE: reserve/test/reserve-test.js
  function test_seneca (line 132) | function test_seneca (fin) {

FILE: timeline/test/timeline-test.js
  function test_seneca (line 77) | function test_seneca (fin) {

FILE: timeline/timeline-logic.js
  function insert (line 13) | function insert (msg, done) {
  function list (line 88) | function list (msg, done) {

FILE: timeline/timeline-shard-service.js
  function resolve_shard (line 8) | function resolve_shard(user) {
Condensed preview — 119 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (140K chars).
[
  {
    "path": ".gitignore",
    "chars": 628,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscov"
  },
  {
    "path": ".travis.yml",
    "chars": 74,
    "preview": "sudo: required\ndist: trusty\nlanguage: node_js\n\nnode_js:\n  - \"6\"\n  - \"node\""
  },
  {
    "path": "LICENSE",
    "chars": 1110,
    "preview": "The MIT License (MIT)\n\nCopyright (c) Richard Rodger and other contributors 2015-2016.\n\nPermission is hereby granted, fre"
  },
  {
    "path": "README.md",
    "chars": 15830,
    "preview": "# ramanujan\n\nThis project is an implementation of a microblogging system (similar\nto the basic functionality of [Twitter"
  },
  {
    "path": "api/api-service.js",
    "chars": 2421,
    "preview": "\"use strict\"\n\nvar PORT = process.env.PORT || process.argv[2] || 0\nvar HOST = process.env.HOST || process.argv[3] || '127"
  },
  {
    "path": "base/base.js",
    "chars": 875,
    "preview": "// node base.js base0 39000 127.0.0.1 127.0.0.1:39000,127.0.0.1:39001\n// node base.js base1 39001 127.0.0.1 127.0.0.1:39"
  },
  {
    "path": "docker/Makefile",
    "chars": 563,
    "preview": "containers :\n\t$(MAKE) -C shared container\n\t$(MAKE) -C api container\n\t$(MAKE) -C base container\n\t$(MAKE) -C entry-cache c"
  },
  {
    "path": "docker/api/Dockerfile",
    "chars": 67,
    "preview": "FROM shared\n\nADD api-service.js .\n\nCMD [\"node\", \"api-service.js\"]\n\n"
  },
  {
    "path": "docker/api/Makefile",
    "chars": 331,
    "preview": "container :\n\tcp ../../api/api-service.js .\n\tdocker build -t api .\n\tdocker images | grep api\n\nrun-single :\n\tdocker servic"
  },
  {
    "path": "docker/api/api-service.js",
    "chars": 2421,
    "preview": "\"use strict\"\n\nvar PORT = process.env.PORT || process.argv[2] || 0\nvar HOST = process.env.HOST || process.argv[3] || '127"
  },
  {
    "path": "docker/base/Dockerfile",
    "chars": 53,
    "preview": "FROM shared\n\nADD base.js .\n\nCMD [\"node\", \"base.js\"]\n\n"
  },
  {
    "path": "docker/base/Makefile",
    "chars": 579,
    "preview": "container :\n\tcp ../../base/base.js .\n\tdocker build -t base .\n\tdocker images | grep base\n\nrun-single-base0:\n\tdocker servi"
  },
  {
    "path": "docker/base/base.js",
    "chars": 875,
    "preview": "// node base.js base0 39000 127.0.0.1 127.0.0.1:39000,127.0.0.1:39001\n// node base.js base1 39001 127.0.0.1 127.0.0.1:39"
  },
  {
    "path": "docker/docker.txt",
    "chars": 1784,
    "preview": "\n# These are development notes NOT instructions!\n\n\n# on host \n\n\ndocker-machine ls\n\ndocker-machine create --driver virtua"
  },
  {
    "path": "docker/entry-cache/Dockerfile",
    "chars": 110,
    "preview": "FROM shared\n\nADD entry-cache-logic.js .\nADD entry-cache-service.js .\n\nCMD [\"node\", \"entry-cache-service.js\"]\n\n"
  },
  {
    "path": "docker/entry-cache/Makefile",
    "chars": 382,
    "preview": "container :\n\tcp ../../entry-cache/entry-cache-*.js .\n\tdocker build -t entry-cache .\n\tdocker images | grep entry-cache\n\nr"
  },
  {
    "path": "docker/entry-cache/entry-cache-logic.js",
    "chars": 563,
    "preview": "'use strict'\n\nvar _ = require('lodash')\n\nmodule.exports = function entry_cache (options) {\n  var seneca = this\n\n  var ca"
  },
  {
    "path": "docker/entry-cache/entry-cache-service.js",
    "chars": 639,
    "preview": "var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'\nvar BASES = (process.env.BASES || process.argv[3] || '').s"
  },
  {
    "path": "docker/entry-store/Dockerfile",
    "chars": 110,
    "preview": "FROM shared\n\nADD entry-store-logic.js .\nADD entry-store-service.js .\n\nCMD [\"node\", \"entry-store-service.js\"]\n\n"
  },
  {
    "path": "docker/entry-store/Makefile",
    "chars": 382,
    "preview": "container :\n\tcp ../../entry-store/entry-store-*.js .\n\tdocker build -t entry-store .\n\tdocker images | grep entry-store\n\nr"
  },
  {
    "path": "docker/entry-store/entry-store-logic.js",
    "chars": 833,
    "preview": "module.exports = function entry_store (options) {\n  var seneca = this\n\n  seneca.add('store:save,kind:entry', function(ms"
  },
  {
    "path": "docker/entry-store/entry-store-service.js",
    "chars": 651,
    "preview": "var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'\nvar BASES = (process.env.BASES || process.argv[3] || '').s"
  },
  {
    "path": "docker/fanout/Dockerfile",
    "chars": 95,
    "preview": "FROM shared\n\nADD fanout-logic.js .\nADD fanout-service.js .\n\nCMD [\"node\", \"fanout-service.js\"]\n\n"
  },
  {
    "path": "docker/fanout/Makefile",
    "chars": 347,
    "preview": "container :\n\tcp ../../fanout/fanout-*.js .\n\tdocker build -t fanout .\n\tdocker images | grep fanout\n\nrun-single :\n\tdocker "
  },
  {
    "path": "docker/fanout/fanout-logic.js",
    "chars": 500,
    "preview": "'use strict'\n\nvar _ = require('lodash')\n\nmodule.exports = function fanout (options) {\n  var seneca = this\n\n  seneca.add("
  },
  {
    "path": "docker/fanout/fanout-service.js",
    "chars": 762,
    "preview": "var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'\nvar BASES = (process.env.BASES || process.argv[3] || '').s"
  },
  {
    "path": "docker/follow/Dockerfile",
    "chars": 95,
    "preview": "FROM shared\n\nADD follow-logic.js .\nADD follow-service.js .\n\nCMD [\"node\", \"follow-service.js\"]\n\n"
  },
  {
    "path": "docker/follow/Makefile",
    "chars": 347,
    "preview": "container :\n\tcp ../../follow/follow-*.js .\n\tdocker build -t follow .\n\tdocker images | grep follow\n\nrun-single :\n\tdocker "
  },
  {
    "path": "docker/follow/follow-logic.js",
    "chars": 2100,
    "preview": "var _ = require('lodash')\n\nmodule.exports = function follow (options) {\n  var seneca = this\n\n  seneca.add('follow:user',"
  },
  {
    "path": "docker/follow/follow-service.js",
    "chars": 607,
    "preview": "var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'\nvar BASES = (process.env.BASES || process.argv[3] || '').s"
  },
  {
    "path": "docker/front/Dockerfile",
    "chars": 67,
    "preview": "FROM shared\n\nADD front.js .\nADD www www\n\nCMD [\"node\", \"front.js\"]\n\n"
  },
  {
    "path": "docker/front/Makefile",
    "chars": 381,
    "preview": "container :\n\tcp ../../front/front.js .\n\tcp -r ../../front/www .\n\tdocker build -t front .\n\tdocker images | grep front\n\nru"
  },
  {
    "path": "docker/front/front.js",
    "chars": 1658,
    "preview": "\"use strict\"\nvar HOST = process.env.HOST || process.argv[2] || '127.0.0.1'\nvar BASES = (process.env.BASES || process.arg"
  },
  {
    "path": "docker/front/www/res/site.css",
    "chars": 663,
    "preview": "* {\n  font-family: arial;\n  padding: 0px;\n  margin: 0px;\n  text-decoration: none;\n}\n\nbody {\n  background-color: #eef;\n}\n"
  },
  {
    "path": "docker/home/Dockerfile",
    "chars": 81,
    "preview": "FROM shared\n\nADD home-service.js .\nADD www www\n\nCMD [\"node\", \"home-service.js\"]\n\n"
  },
  {
    "path": "docker/home/Makefile",
    "chars": 362,
    "preview": "container :\n\tcp ../../home/home-service.js .\n\tcp -r ../../home/www .\n\tdocker build -t home .\n\tdocker images | grep home\n"
  },
  {
    "path": "docker/home/home-service.js",
    "chars": 2058,
    "preview": "\"use strict\"\n\nvar PORT = process.env.PORT || process.argv[2] || 0\nvar HOST = process.env.HOST || process.argv[3] || '127"
  },
  {
    "path": "docker/home/www/home.html",
    "chars": 604,
    "preview": "<form action=\"/api/post/{{user}}\" method=\"post\">\n<input type=\"text\" name=\"text\">\n<input type=\"hidden\" name=\"from\" value="
  },
  {
    "path": "docker/home/www/layout.html",
    "chars": 369,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <title>Microblog</title>\n  <link rel=\"stylesheet\" href=\"/res/site.css\">\n</head"
  },
  {
    "path": "docker/index/Dockerfile",
    "chars": 92,
    "preview": "FROM shared\n\nADD index-logic.js .\nADD index-service.js .\n\nCMD [\"node\", \"index-service.js\"]\n\n"
  },
  {
    "path": "docker/index/Makefile",
    "chars": 340,
    "preview": "container :\n\tcp ../../index/index-*.js .\n\tdocker build -t index .\n\tdocker images | grep index\n\nrun-single :\n\tdocker serv"
  },
  {
    "path": "docker/index/index-logic.js",
    "chars": 2273,
    "preview": "// Business logic for the index microservice.\n// Provides a full text search index for microblog entries.\n\n\n// Modules p"
  },
  {
    "path": "docker/index/index-service.js",
    "chars": 763,
    "preview": "var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'\nvar BASES = (process.env.BASES || process.argv[3] || '').s"
  },
  {
    "path": "docker/mine/Dockerfile",
    "chars": 81,
    "preview": "FROM shared\n\nADD mine-service.js .\nADD www www\n\nCMD [\"node\", \"mine-service.js\"]\n\n"
  },
  {
    "path": "docker/mine/Makefile",
    "chars": 362,
    "preview": "container :\n\tcp ../../mine/mine-service.js .\n\tcp -r ../../mine/www .\n\tdocker build -t mine .\n\tdocker images | grep mine\n"
  },
  {
    "path": "docker/mine/mine-service.js",
    "chars": 1989,
    "preview": "\"use strict\"\n\nvar PORT = process.env.PORT || process.argv[2] || 0\nvar HOST = process.env.HOST || process.argv[3] || 0\nva"
  },
  {
    "path": "docker/mine/www/home.html",
    "chars": 604,
    "preview": "<form action=\"/api/post/{{user}}\" method=\"post\">\n<input type=\"text\" name=\"text\">\n<input type=\"hidden\" name=\"from\" value="
  },
  {
    "path": "docker/mine/www/layout.html",
    "chars": 369,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <title>Microblog</title>\n  <link rel=\"stylesheet\" href=\"/res/site.css\">\n</head"
  },
  {
    "path": "docker/mine/www/mine.html",
    "chars": 329,
    "preview": "<form action=\"/api/post/{{user}}\" method=\"post\">\n<input type=\"text\" name=\"text\">\n<input type=\"hidden\" name=\"from\" value="
  },
  {
    "path": "docker/post/Dockerfile",
    "chars": 89,
    "preview": "FROM shared\n\nADD post-logic.js .\nADD post-service.js .\n\nCMD [\"node\", \"post-service.js\"]\n\n"
  },
  {
    "path": "docker/post/Makefile",
    "chars": 333,
    "preview": "container :\n\tcp ../../post/post-*.js .\n\tdocker build -t post .\n\tdocker images | grep post\n\nrun-single :\n\tdocker service "
  },
  {
    "path": "docker/post/post-logic.js",
    "chars": 367,
    "preview": "module.exports = function post (options) {\n  var seneca = this\n\n  seneca.add('post:entry', function(msg, done) {\n    var"
  },
  {
    "path": "docker/post/post-service.js",
    "chars": 602,
    "preview": "var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'\nvar BASES = (process.env.BASES || process.argv[3] || '').s"
  },
  {
    "path": "docker/ramanujan.yml",
    "chars": 3731,
    "preview": "\nversion: '3'\n\nnetworks:\n  ramanujan:\n    driver: overlay\n\nservices:\n  base0:\n    image: base\n    environment:\n      TAG"
  },
  {
    "path": "docker/repl/Dockerfile",
    "chars": 100,
    "preview": "FROM shared\n\nADD repl-service.js .\nADD monitor.js .\n\nEXPOSE 10001\n\nCMD [\"node\", \"repl-service.js\"]\n\n"
  },
  {
    "path": "docker/repl/Makefile",
    "chars": 408,
    "preview": "container :\n\tcp ../../repl/repl-service.js .\n\tcp ../../monitor/monitor.js .\n\tdocker build -t repl .\n\tdocker images | gre"
  },
  {
    "path": "docker/repl/monitor.js",
    "chars": 289,
    "preview": "var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'\nvar BASES = (process.env.BASES || process.argv[3] || '127."
  },
  {
    "path": "docker/repl/repl-service.js",
    "chars": 1110,
    "preview": "var REPL_PORT = parseInt(process.env.REPL_PORT || process.argv[2] || 10001)\nvar REPL_HOST = process.env.REPL_HOST || pro"
  },
  {
    "path": "docker/reserve/Dockerfile",
    "chars": 98,
    "preview": "FROM shared\n\nADD reserve-logic.js .\nADD reserve-service.js .\n\nCMD [\"node\", \"reserve-service.js\"]\n\n"
  },
  {
    "path": "docker/reserve/Makefile",
    "chars": 354,
    "preview": "container :\n\tcp ../../reserve/reserve-*.js .\n\tdocker build -t reserve .\n\tdocker images | grep reserve\n\nrun-single :\n\tdoc"
  },
  {
    "path": "docker/reserve/reserve-logic.js",
    "chars": 1184,
    "preview": "var _ = require('lodash')\n\nmodule.exports = function follow (options) {\n  var seneca = this\n\n  var interval = options.in"
  },
  {
    "path": "docker/reserve/reserve-service.js",
    "chars": 590,
    "preview": "var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'\nvar BASES = (process.env.BASES || process.argv[3] || '').s"
  },
  {
    "path": "docker/search/Dockerfile",
    "chars": 85,
    "preview": "FROM shared\n\nADD search-service.js .\nADD www www\n\nCMD [\"node\", \"search-service.js\"]\n\n"
  },
  {
    "path": "docker/search/Makefile",
    "chars": 378,
    "preview": "container :\n\tcp ../../search/search-service.js .\n\tcp -r ../../search/www .\n\tdocker build -t search .\n\tdocker images | gr"
  },
  {
    "path": "docker/search/search-service.js",
    "chars": 2727,
    "preview": "\"use strict\"\n\nvar PORT = process.env.PORT || process.argv[2] || 0\nvar HOST = process.env.HOST || process.argv[3] || 0\nva"
  },
  {
    "path": "docker/search/www/home.html",
    "chars": 604,
    "preview": "<form action=\"/api/post/{{user}}\" method=\"post\">\n<input type=\"text\" name=\"text\">\n<input type=\"hidden\" name=\"from\" value="
  },
  {
    "path": "docker/search/www/layout.html",
    "chars": 369,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <title>Microblog</title>\n  <link rel=\"stylesheet\" href=\"/res/site.css\">\n</head"
  },
  {
    "path": "docker/search/www/search.html",
    "chars": 581,
    "preview": "<form action=\"/search/{{user}}\" method=\"post\">\n<input type=\"text\" name=\"query\">\n<input type=\"submit\" value=\"search\">\n</f"
  },
  {
    "path": "docker/shared/Dockerfile",
    "chars": 115,
    "preview": "\nFROM mhart/alpine-node:4\n\nRUN apk add --no-cache make gcc g++ python git\n\nADD package.json /\n\nRUN npm install\n\n\n\n\n"
  },
  {
    "path": "docker/shared/Makefile",
    "chars": 153,
    "preview": "\ncontainer :\n\tcp ../../package.json .\n\tdocker build -t shared .\n\tdocker images | grep shared\n\nclean :\n\trm -f *~\n\trm -f *"
  },
  {
    "path": "docker/shared/package.json",
    "chars": 1204,
    "preview": "{\n  \"name\": \"ramanujan\",\n  \"version\": \"0.0.1\",\n  \"description\": \"ramanujan\",\n  \"main\": \"ramanujan.js\",\n  \"scripts\": {\n  "
  },
  {
    "path": "docker/timeline/Dockerfile",
    "chars": 89,
    "preview": "FROM shared\n\nADD timeline-shard-service.js .\n\nCMD [\"node\", \"timeline-shard-service.js\"]\n\n"
  },
  {
    "path": "docker/timeline/Makefile",
    "chars": 373,
    "preview": "container :\n\tcp ../../timeline/timeline-shard-service.js .\n\tdocker build -t timeline .\n\tdocker images | grep timeline\n\nr"
  },
  {
    "path": "docker/timeline/timeline-shard-service.js",
    "chars": 1180,
    "preview": "var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'\nvar BASES = (process.env.BASES || process.argv[3] || '').s"
  },
  {
    "path": "docker/timeline-shard/Dockerfile",
    "chars": 101,
    "preview": "FROM shared\n\nADD timeline-service.js .\nADD timeline-logic.js .\n\nCMD [\"node\", \"timeline-service.js\"]\n\n"
  },
  {
    "path": "docker/timeline-shard/Makefile",
    "chars": 675,
    "preview": "container :\n\tcp ../../timeline/timeline-logic.js .\n\tcp ../../timeline/timeline-service.js .\n\tdocker build -t timeline-sh"
  },
  {
    "path": "docker/timeline-shard/timeline-logic.js",
    "chars": 2499,
    "preview": "'use strict'\n\nvar _ = require('lodash')\n\nmodule.exports = function timeline (options) {\n  var seneca = this\n\n\n  seneca.a"
  },
  {
    "path": "docker/timeline-shard/timeline-service.js",
    "chars": 679,
    "preview": "var SHARD = process.env.SHARD || process.argv[2] || 0\nvar HOST = process.env.HOST || process.argv[3] || '127.0.0.1'\nvar "
  },
  {
    "path": "entry-cache/entry-cache-logic.js",
    "chars": 563,
    "preview": "'use strict'\n\nvar _ = require('lodash')\n\nmodule.exports = function entry_cache (options) {\n  var seneca = this\n\n  var ca"
  },
  {
    "path": "entry-cache/entry-cache-service.js",
    "chars": 639,
    "preview": "var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'\nvar BASES = (process.env.BASES || process.argv[3] || '').s"
  },
  {
    "path": "entry-cache/test/entry-cache-test.js",
    "chars": 4309,
    "preview": "// Unit test for the entry-cache microservice.\n// Uses https://github.com/hapijs/lab but easy to refactor for other unit"
  },
  {
    "path": "entry-store/entry-store-logic.js",
    "chars": 833,
    "preview": "module.exports = function entry_store (options) {\n  var seneca = this\n\n  seneca.add('store:save,kind:entry', function(ms"
  },
  {
    "path": "entry-store/entry-store-service.js",
    "chars": 651,
    "preview": "var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'\nvar BASES = (process.env.BASES || process.argv[3] || '').s"
  },
  {
    "path": "entry-store/test/entry-store-test.js",
    "chars": 2816,
    "preview": "// Unit test for the entry-store microservice.\n// Uses https://github.com/hapijs/lab but easy to refactor for other unit"
  },
  {
    "path": "fanout/fanout-logic.js",
    "chars": 500,
    "preview": "'use strict'\n\nvar _ = require('lodash')\n\nmodule.exports = function fanout (options) {\n  var seneca = this\n\n  seneca.add("
  },
  {
    "path": "fanout/fanout-service.js",
    "chars": 762,
    "preview": "var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'\nvar BASES = (process.env.BASES || process.argv[3] || '').s"
  },
  {
    "path": "fanout/test/fanout-test.js",
    "chars": 1670,
    "preview": "// Unit test for the fanout microservice.\n// Uses https://github.com/hapijs/lab but easy to refactor for other unit test"
  },
  {
    "path": "follow/follow-logic.js",
    "chars": 2100,
    "preview": "var _ = require('lodash')\n\nmodule.exports = function follow (options) {\n  var seneca = this\n\n  seneca.add('follow:user',"
  },
  {
    "path": "follow/follow-service.js",
    "chars": 607,
    "preview": "var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'\nvar BASES = (process.env.BASES || process.argv[3] || '').s"
  },
  {
    "path": "follow/test/follow-test.js",
    "chars": 2621,
    "preview": "// Unit test for the follow microservice.\n// Uses https://github.com/hapijs/lab but easy to refactor for other unit test"
  },
  {
    "path": "front/front.js",
    "chars": 1658,
    "preview": "\"use strict\"\nvar HOST = process.env.HOST || process.argv[2] || '127.0.0.1'\nvar BASES = (process.env.BASES || process.arg"
  },
  {
    "path": "front/www/res/site.css",
    "chars": 663,
    "preview": "* {\n  font-family: arial;\n  padding: 0px;\n  margin: 0px;\n  text-decoration: none;\n}\n\nbody {\n  background-color: #eef;\n}\n"
  },
  {
    "path": "fuge/fuge.yml",
    "chars": 1983,
    "preview": "fuge_global:\n  tail: true\n  monitor: false\n  auto_generate_environment: false\n  monitor_excludes:\n    - '**/node_modules"
  },
  {
    "path": "home/home-service.js",
    "chars": 2058,
    "preview": "\"use strict\"\n\nvar PORT = process.env.PORT || process.argv[2] || 0\nvar HOST = process.env.HOST || process.argv[3] || '127"
  },
  {
    "path": "home/www/home.html",
    "chars": 604,
    "preview": "<form action=\"/api/post/{{user}}\" method=\"post\">\n<input type=\"text\" name=\"text\">\n<input type=\"hidden\" name=\"from\" value="
  },
  {
    "path": "home/www/layout.html",
    "chars": 369,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <title>Microblog</title>\n  <link rel=\"stylesheet\" href=\"/res/site.css\">\n</head"
  },
  {
    "path": "index/index-logic.js",
    "chars": 2273,
    "preview": "// Business logic for the index microservice.\n// Provides a full text search index for microblog entries.\n\n\n// Modules p"
  },
  {
    "path": "index/index-service.js",
    "chars": 763,
    "preview": "var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'\nvar BASES = (process.env.BASES || process.argv[3] || '').s"
  },
  {
    "path": "index/test/index-test.js",
    "chars": 2055,
    "preview": "// Unit test for the index microservice.\n// Uses https://github.com/hapijs/lab but easy to refactor for other unit teste"
  },
  {
    "path": "mine/mine-service.js",
    "chars": 1989,
    "preview": "\"use strict\"\n\nvar PORT = process.env.PORT || process.argv[2] || 0\nvar HOST = process.env.HOST || process.argv[3] || 0\nva"
  },
  {
    "path": "mine/www/layout.html",
    "chars": 369,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <title>Microblog</title>\n  <link rel=\"stylesheet\" href=\"/res/site.css\">\n</head"
  },
  {
    "path": "mine/www/mine.html",
    "chars": 329,
    "preview": "<form action=\"/api/post/{{user}}\" method=\"post\">\n<input type=\"text\" name=\"text\">\n<input type=\"hidden\" name=\"from\" value="
  },
  {
    "path": "monitor/monitor.js",
    "chars": 289,
    "preview": "var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'\nvar BASES = (process.env.BASES || process.argv[3] || '127."
  },
  {
    "path": "package.json",
    "chars": 1204,
    "preview": "{\n  \"name\": \"ramanujan\",\n  \"version\": \"0.0.1\",\n  \"description\": \"ramanujan\",\n  \"main\": \"ramanujan.js\",\n  \"scripts\": {\n  "
  },
  {
    "path": "post/post-logic.js",
    "chars": 367,
    "preview": "module.exports = function post (options) {\n  var seneca = this\n\n  seneca.add('post:entry', function(msg, done) {\n    var"
  },
  {
    "path": "post/post-service.js",
    "chars": 602,
    "preview": "var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'\nvar BASES = (process.env.BASES || process.argv[3] || '').s"
  },
  {
    "path": "post/test/post-test.js",
    "chars": 1816,
    "preview": "// Unit test for the post microservice.\n// Uses https://github.com/hapijs/lab but easy to refactor for other unit tester"
  },
  {
    "path": "repl/repl-service.js",
    "chars": 1110,
    "preview": "var REPL_PORT = parseInt(process.env.REPL_PORT || process.argv[2] || 10001)\nvar REPL_HOST = process.env.REPL_HOST || pro"
  },
  {
    "path": "reserve/reserve-logic.js",
    "chars": 1184,
    "preview": "var _ = require('lodash')\n\nmodule.exports = function follow (options) {\n  var seneca = this\n\n  var interval = options.in"
  },
  {
    "path": "reserve/reserve-service.js",
    "chars": 590,
    "preview": "var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'\nvar BASES = (process.env.BASES || process.argv[3] || '').s"
  },
  {
    "path": "reserve/test/reserve-test.js",
    "chars": 3214,
    "preview": "// Unit test for the reserve microservice.\n// Uses https://github.com/hapijs/lab but easy to refactor for other unit tes"
  },
  {
    "path": "search/search-service.js",
    "chars": 2727,
    "preview": "\"use strict\"\n\nvar PORT = process.env.PORT || process.argv[2] || 0\nvar HOST = process.env.HOST || process.argv[3] || 0\nva"
  },
  {
    "path": "search/www/layout.html",
    "chars": 369,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <title>Microblog</title>\n  <link rel=\"stylesheet\" href=\"/res/site.css\">\n</head"
  },
  {
    "path": "search/www/search.html",
    "chars": 581,
    "preview": "<form action=\"/search/{{user}}\" method=\"post\">\n<input type=\"text\" name=\"query\">\n<input type=\"submit\" value=\"search\">\n</f"
  },
  {
    "path": "start.sh",
    "chars": 1262,
    "preview": "HOST=\"127.0.0.1\"\nBASES=\"127.0.0.1:39000,127.0.0.1:39001\"\nOPTS=\"\"\n\n# for demos use OPTS = '--seneca.options.debug.undead="
  },
  {
    "path": "timeline/test/timeline-test.js",
    "chars": 3371,
    "preview": "// Unit test for the timeline microservice.\n// Uses https://github.com/hapijs/lab but easy to refactor for other unit te"
  },
  {
    "path": "timeline/timeline-logic.js",
    "chars": 2499,
    "preview": "'use strict'\n\nvar _ = require('lodash')\n\nmodule.exports = function timeline (options) {\n  var seneca = this\n\n\n  seneca.a"
  },
  {
    "path": "timeline/timeline-service.js",
    "chars": 679,
    "preview": "var SHARD = process.env.SHARD || process.argv[2] || 0\nvar HOST = process.env.HOST || process.argv[3] || '127.0.0.1'\nvar "
  },
  {
    "path": "timeline/timeline-shard-service.js",
    "chars": 1180,
    "preview": "var HOST = process.env.HOST || process.argv[2] || '127.0.0.1'\nvar BASES = (process.env.BASES || process.argv[3] || '').s"
  }
]

About this extraction

This page contains the full source code of the senecajs/ramanujan GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 119 files (123.9 KB), approximately 37.9k tokens, and a symbol index with 30 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!