diff --git a/LICENSE b/LICENSE index 4e81bbe..d424397 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014 FeedLabs +Copyright (c) 2015 FeedLabs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f03b010 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +TEST?=./... + +default: test + +bin: + @sh -c "$(CURDIR)/scripts/build.sh" + +dev: + @TF_DEV=1 sh -c "$(CURDIR)/scripts/build.sh" + +test: + go test $(TEST) $(TESTARGS) -timeout=10s + +testrace: + go test -race $(TEST) $(TESTARGS) + +updatedeps: + go get -d -v -p 2 ./... + +.PHONY: bin default test updatedeps diff --git a/README.md b/README.md index 466b630..b56aa99 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ elasticfeed #### Internal workflow ![elasticfeed-overview - mission](https://cloud.githubusercontent.com/assets/1843523/7103001/212978e0-e095-11e4-8b23-091adefe3cb7.png) -#### Development +#### Development environment Create symbolic link from you directory to `GOPATH` ``` cd to-your-project @@ -14,6 +14,50 @@ mkdir -p $GOPATH/src/github.com/feedlabs ln -s $(pwd) $GOPATH/src/github.com/feedlabs/elasticfeed ``` +#### Developing Elasticfeed +If you wish to work on Elasticfeed itself or any of its built-in providers, +you'll first need [Go](http://www.golang.org) installed (version 1.2+ is +_required_). Make sure Go is properly installed, including setting up +a [GOPATH](http://golang.org/doc/code.html#GOPATH). + +Next, install the following software packages, which are needed for some dependencies: + +- [Bazaar](http://bazaar.canonical.com/en/) +- [Git](http://git-scm.com/) +- [Mercurial](http://mercurial.selenic.com/) + +Then, install [Gox](https://github.com/mitchellh/gox), which is used +as a compilation tool on top of Go: + + $ go get -u github.com/mitchellh/gox + +Next, clone this repository into `$GOPATH/src/github.com/elasticfeed/elasticfeed`. +Install the necessary dependencies by running `make updatedeps` and then just +type `make`. This will compile some more dependencies and then run the tests. If +this exits with exit status 0, then everything is working! + + $ make updatedeps + ... + $ make + ... + +To compile a development version of Elasticfeed and the built-in plugins, +run `make dev`. This will put Elasticfeed binaries in the `bin` folder: + + $ make dev + ... + $ bin/elasticfeed + ... + + +If you're developing a specific package, you can run tests for just that +package by specifying the `TEST` variable. For example below, only +`elasticfeed` package tests will be run. + + $ make test TEST=./elasticfeed + ... + + #### Run `go run api.go` @@ -31,7 +75,7 @@ $ npm install apidoc -g ##### Generate ``` -$ apidoc -i public/ -o docs/api +$ apidoc -i service/db -o docs/api ``` ##### View diff --git a/api.go b/api.go index a283a28..afed378 100644 --- a/api.go +++ b/api.go @@ -1,10 +1,21 @@ package main import ( - _ "github.com/feedlabs/elasticfeed/service" - "github.com/feedlabs/feedify" + "github.com/feedlabs/elasticfeed/service" + "github.com/feedlabs/elasticfeed/plugin" + "github.com/feedlabs/elasticfeed/workflow" + "github.com/feedlabs/elasticfeed/event" + "github.com/feedlabs/elasticfeed/resource" + "github.com/feedlabs/elasticfeed/elasticfeed" ) func main() { - feedify.Run() + rm := resource.NewResourceManager() + em := event.NewEventManager() + pm := plugin.NewPluginManager(rm) + wm := workflow.NewWorkflowManager(nil, pm, em) + sm := service.NewServiceManager() + + ServerEngine := elasticfeed.NewElasticfeed(rm, em, sm, pm, wm) + ServerEngine.Run() } diff --git a/apidoc.json b/apidoc.json index 31913ae..50e6b96 100644 --- a/apidoc.json +++ b/apidoc.json @@ -6,11 +6,11 @@ "url": "https://api.feedlabs.com/v1/", "header": { "title": "Principles", - "filename": "docs/apidoc-header.md" + "filename": "docs/apidoc/apidoc-header.md" }, "footer": { "title": "Appendix", - "filename": "docs/apidoc-footer.md" + "filename": "docs/apidoc/apidoc-footer.md" }, "order": [ "Application", diff --git a/helper/auth.go b/common/auth.go similarity index 96% rename from helper/auth.go rename to common/auth.go index 3e73119..aa016e9 100644 --- a/helper/auth.go +++ b/common/auth.go @@ -1,10 +1,10 @@ -package helper +package common import ( auth "github.com/abbot/go-http-auth" "github.com/astaxie/beego/context" "github.com/feedlabs/elasticfeed/resource" - "github.com/feedlabs/elasticfeed/helper/config" + "github.com/feedlabs/elasticfeed/common/config" ) var ( diff --git a/helper/config/general.go b/common/config/general.go similarity index 100% rename from helper/config/general.go rename to common/config/general.go diff --git a/helper/crypt.go b/common/crypt.go similarity index 95% rename from helper/crypt.go rename to common/crypt.go index a9d84a0..dc14e5d 100644 --- a/helper/crypt.go +++ b/common/crypt.go @@ -1,4 +1,4 @@ -package helper +package common import ( "crypto/md5" diff --git a/helper/ids.go b/common/ids.go similarity index 90% rename from helper/ids.go rename to common/ids.go index 40f0510..9da85b9 100644 --- a/helper/ids.go +++ b/common/ids.go @@ -1,4 +1,4 @@ -package helper +package common import ( "github.com/feedlabs/elasticfeed/resource" diff --git a/helper/net.go b/common/net.go similarity index 93% rename from helper/net.go rename to common/net.go index a8171d2..dc78838 100644 --- a/helper/net.go +++ b/common/net.go @@ -1,4 +1,4 @@ -package helper +package common import ( "net" diff --git a/common/uuid/uuid.go b/common/uuid/uuid.go new file mode 100644 index 0000000..d8b9830 --- /dev/null +++ b/common/uuid/uuid.go @@ -0,0 +1,24 @@ +package uuid + +import ( + "crypto/rand" + "fmt" + "time" +) + +// Generates a time ordered UUID. Top 32 bits are a timestamp, +// bottom 96 are random. +func TimeOrderedUUID() string { + unix := uint32(time.Now().UTC().Unix()) + + b := make([]byte, 12) + n, err := rand.Read(b) + if n != len(b) { + err = fmt.Errorf("Not enough entropy available") + } + if err != nil { + panic(err) + } + return fmt.Sprintf("%08x-%04x-%04x-%04x-%04x%08x", + unix, b[0:2], b[2:4], b[4:6], b[6:8], b[8:]) +} diff --git a/common/uuid/uuid_test.go b/common/uuid/uuid_test.go new file mode 100644 index 0000000..8a853f1 --- /dev/null +++ b/common/uuid/uuid_test.go @@ -0,0 +1,12 @@ +package uuid + +import ( + "testing" +) + +func TestTimeOrderedUuid(t *testing.T) { + uuid := TimeOrderedUUID() + if len(uuid) != 36 { + t.Fatalf("bad: %s", uuid) + } +} diff --git a/config.go b/config.go new file mode 100644 index 0000000..76f0ae8 --- /dev/null +++ b/config.go @@ -0,0 +1,224 @@ +package main + +import ( + "encoding/json" + "io" + "log" + "os/exec" + "path/filepath" + "strings" + + "github.com/mitchellh/osext" + "github.com/mitchellh/elasticfeed/elasticfeed" + "github.com/mitchellh/elasticfeed/elasticfeed/plugin" +) + +// EnvConfig is the global EnvironmentConfig we use to initialize the CLI. +var EnvConfig elasticfeed.EnvironmentConfig + +type config struct { + PluginMinPort uint + PluginMaxPort uint + + Pipelines map[string]string + Indexers map[string]string + Crawlers map[string]string + Scenarios map[string]string +} + +// ConfigFile returns the default path to the configuration file. On +// Unix-like systems this is the ".elasticfeedconfig" file in the home directory. +// On Windows, this is the "elasticfeed.config" file in the application data +// directory. +func ConfigFile() (string, error) { + return configFile() +} + +// ConfigDir returns the configuration directory for Elasticfeed. +func ConfigDir() (string, error) { + return configDir() +} + +// Decodes configuration in JSON format from the given io.Reader into +// the config object pointed to. +func decodeConfig(r io.Reader, c *config) error { + decoder := json.NewDecoder(r) + return decoder.Decode(c) +} + +// Discover discovers plugins. +// +// This looks in the directory of the executable and the CWD, in that +// order for priority. +func (c *config) Discover() error { + // Next, look in the same directory as the executable. Any conflicts + // will overwrite those found in our current directory. + exePath, err := osext.Executable() + if err != nil { + log.Printf("[ERR] Error loading exe directory: %s", err) + } else { + if err := c.discover(filepath.Dir(exePath)); err != nil { + return err + } + } + + // Look in the plugins directory + dir, err := ConfigDir() + if err != nil { + log.Printf("[ERR] Error loading config directory: %s", err) + } else { + if err := c.discover(filepath.Join(dir, "plugins")); err != nil { + return err + } + } + + // Look in the cwd. + if err := c.discover("."); err != nil { + return err + } + + return nil +} + +func (c *config) LoadPiplines(name string) (elasticfeed.Pipeline, error) { + log.Printf("Loading provisioner: %s\n", name) + bin, ok := c.Pipelines[name] + if !ok { + log.Printf("Pipelines not found: %s\n", name) + return nil, nil + } + + return c.pluginClient(bin).Pipeline() +} + +func (c *config) LoadIndexers(name string) (elasticfeed.Indexer, error) { + log.Printf("Loading provisioner: %s\n", name) + bin, ok := c.Indexers[name] + if !ok { + log.Printf("Indexers not found: %s\n", name) + return nil, nil + } + + return c.pluginClient(bin).Indexer() +} + +func (c *config) LoadCrawlers(name string) (elasticfeed.Crawler, error) { + log.Printf("Loading provisioner: %s\n", name) + bin, ok := c.Crawlers[name] + if !ok { + log.Printf("Crawlers not found: %s\n", name) + return nil, nil + } + + return c.pluginClient(bin).Crawler() +} + +func (c *config) LoadScenarios(name string) (elasticfeed.Scenario, error) { + log.Printf("Loading provisioner: %s\n", name) + bin, ok := c.Scenarios[name] + if !ok { + log.Printf("Scenarios not found: %s\n", name) + return nil, nil + } + + return c.pluginClient(bin).Scenario() +} + +func (c *config) discover(path string) error { + var err error + + if !filepath.IsAbs(path) { + path, err = filepath.Abs(path) + if err != nil { + return err + } + } + + err = c.discoverSingle( + filepath.Join(path, "elasticfeed-pipeline-*"), &c.Pipelines) + if err != nil { + return err + } + + err = c.discoverSingle( + filepath.Join(path, "elasticfeed-indexer-*"), &c.Indexers) + if err != nil { + return err + } + + err = c.discoverSingle( + filepath.Join(path, "elasticfeed-crawler-*"), &c.Crawlers) + if err != nil { + return err + } + + err = c.discoverSingle( + filepath.Join(path, "elasticfeed-scenario-*"), &c.Scenarios) + if err != nil { + return err + } + + return nil +} + +func (c *config) discoverSingle(glob string, m *map[string]string) error { + matches, err := filepath.Glob(glob) + if err != nil { + return err + } + + if *m == nil { + *m = make(map[string]string) + } + + prefix := filepath.Base(glob) + prefix = prefix[:strings.Index(prefix, "*")] + for _, match := range matches { + file := filepath.Base(match) + + // If the filename has a ".", trim up to there + if idx := strings.Index(file, "."); idx >= 0 { + file = file[:idx] + } + + // Look for foo-bar-baz. The plugin name is "baz" + plugin := file[len(prefix):] + log.Printf("[DEBUG] Discoverd plugin: %s = %s", plugin, match) + (*m)[plugin] = match + } + + return nil +} + +func (c *config) pluginClient(path string) *plugin.Client { + originalPath := path + + // First attempt to find the executable by consulting the PATH. + path, err := exec.LookPath(path) + if err != nil { + // If that doesn't work, look for it in the same directory + // as the `packer` executable (us). + log.Printf("Plugin could not be found. Checking same directory as executable.") + exePath, err := osext.Executable() + if err != nil { + log.Printf("Couldn't get current exe path: %s", err) + } else { + log.Printf("Current exe path: %s", exePath) + path = filepath.Join(filepath.Dir(exePath), filepath.Base(originalPath)) + } + } + + // If everything failed, just use the original path and let the error + // bubble through. + if path == "" { + path = originalPath + } + + log.Printf("Creating plugin client for path: %s", path) + var config plugin.ClientConfig + config.Cmd = exec.Command(path) + config.Managed = true + config.MinPort = c.PluginMinPort + config.MaxPort = c.PluginMaxPort + return plugin.NewClient(&config) +} diff --git a/config_unix.go b/config_unix.go new file mode 100644 index 0000000..2c8a7a3 --- /dev/null +++ b/config_unix.go @@ -0,0 +1,54 @@ +// +build darwin freebsd linux netbsd openbsd + +package main + +import ( + "bytes" + "errors" + "log" + "os" + "os/exec" + "path/filepath" + "strings" +) + +func configFile() (string, error) { + dir, err := homeDir() + if err != nil { + return "", err + } + + return filepath.Join(dir, ".packerconfig"), nil +} + +func configDir() (string, error) { + dir, err := homeDir() + if err != nil { + return "", err + } + + return filepath.Join(dir, ".packer.d"), nil +} + +func homeDir() (string, error) { + // First prefer the HOME environmental variable + if home := os.Getenv("HOME"); home != "" { + log.Printf("Detected home directory from env var: %s", home) + return home, nil + } + + // If that fails, try the shell + var stdout bytes.Buffer + cmd := exec.Command("sh", "-c", "eval echo ~$USER") + cmd.Stdout = &stdout + if err := cmd.Run(); err != nil { + return "", err + } + + result := strings.TrimSpace(stdout.String()) + if result == "" { + return "", errors.New("blank output") + } + + return result, nil +} diff --git a/config_windows.go b/config_windows.go new file mode 100644 index 0000000..fa3ab94 --- /dev/null +++ b/config_windows.go @@ -0,0 +1,46 @@ +// +build windows + +package main + +import ( + "path/filepath" + "syscall" + "unsafe" +) + +var ( + shell = syscall.MustLoadDLL("Shell32.dll") + getFolderPath = shell.MustFindProc("SHGetFolderPathW") +) + +const CSIDL_APPDATA = 26 + +func configFile() (string, error) { + dir, err := homeDir() + if err != nil { + return "", err + } + + return filepath.Join(dir, "packer.config"), nil +} + +func configDir() (string, error) { + dir, err := homeDir() + if err != nil { + return "", err + } + + return filepath.Join(dir, "packer.d"), nil +} + +func homeDir() (string, error) { + b := make([]uint16, syscall.MAX_PATH) + + // See: http://msdn.microsoft.com/en-us/library/windows/desktop/bb762181(v=vs.85).aspx + r, _, err := getFolderPath.Call(0, CSIDL_APPDATA, 0, 0, uintptr(unsafe.Pointer(&b[0]))) + if uint32(r) != 0 { + return "", err + } + + return syscall.UTF16ToString(b), nil +} diff --git a/docs/examples/javascript.md b/docs/examples/javascript.md deleted file mode 100644 index 9055496..0000000 --- a/docs/examples/javascript.md +++ /dev/null @@ -1,39 +0,0 @@ -### JS client - -#### Usage -``` - window.onload = function() { - elasticfeed.init({ - channel: { - url: 'ws://localhost:80', - transport: 'ws' - } - }); - - feed = elasticfeed.initFeed('000001', { - outputContainerId: 'my-elastic-feed-1', - stylerFunction: function(data) { - return '
' + data + '
'; - } - }); - - feed.channel.on('join', function(chid, ts) { - feed1.addEntry(chid + " joined the chat room"); - }); - - feed.channel.on('leave', function(chid, ts) { - feed1.addEntry(chid + " left the chat room"); - }); - - window['socket'] = feed1.socket; - } -``` - -#### Test data broadcast -``` - // single feed - socket.send({Type:1, Timestamp:1111111, Content: {Type:3, Timestamp:22222, Id: "000001", Content: "data-examples"}}) - - // all feeds in the view on the chanel - socket.send({Type:1, Timestamp:1111111, Content: {Type:3, Timestamp:22222, Id: "*", Content: "data-examples"}}) -``` diff --git a/elasticfeed/elasticfeed.go b/elasticfeed/elasticfeed.go new file mode 100644 index 0000000..b7c51a4 --- /dev/null +++ b/elasticfeed/elasticfeed.go @@ -0,0 +1,51 @@ +package elasticfeed + +import ( + "github.com/feedlabs/elasticfeed/plugin" + "github.com/feedlabs/elasticfeed/workflow" + "github.com/feedlabs/elasticfeed/service" + "github.com/feedlabs/elasticfeed/event" + "github.com/feedlabs/elasticfeed/resource" + + "github.com/feedlabs/feedify" +) + +type Elasticfeed struct { + rm *resource.ResourceManager + em *event.EventManager + sm *service.ServiceManager + pm *plugin.PluginManager + wm *workflow.WorkflowManager +} + +func (this *Elasticfeed) GetEventManager() *event.EventManager { + return this.em +} + +func (this *Elasticfeed) GetResourceManager() *resource.ResourceManager { + return this.rm +} + +func (this *Elasticfeed) GetServiceManager() *service.ServiceManager { + return this.sm +} + +func (this *Elasticfeed) GetPluginManager() *plugin.PluginManager { + return this.pm +} + +func (this *Elasticfeed) GetWorkflowManager() *workflow.WorkflowManager { + return this.wm +} + +func (this *Elasticfeed) Run() { + this.GetResourceManager().Init() + this.GetServiceManager().Init() + + feedify.SetStaticPath("/static", "public") + feedify.Run() +} + +func NewElasticfeed(rm *resource.ResourceManager, em *event.EventManager, sm *service.ServiceManager, pm *plugin.PluginManager, wm *workflow.WorkflowManager) *Elasticfeed { + return &Elasticfeed{rm, em, sm, pm, wm} +} diff --git a/event/event.go b/event/event.go new file mode 100644 index 0000000..6831249 --- /dev/null +++ b/event/event.go @@ -0,0 +1,12 @@ +package event + +type Event struct { + data interface{} + + eventGroup string + eventName string +} + +func NewEvent(data interface{}) *Event { + return &Event{data, "", ""} +} diff --git a/event/manager.go b/event/manager.go new file mode 100644 index 0000000..5e8fd45 --- /dev/null +++ b/event/manager.go @@ -0,0 +1,46 @@ +package event + +const ( + EVENT_STORING = "storing" + EVENT_PROCESSING = "processing" + EVENT_DISTRIBUTING = "distributing" + EVENT_LEARNING = "learning" + + EVENT_STORING_CREATE_ENTRY = "create-entry" + EVENT_PROCESSING_FEED_MAINTAINER = "feed-maintainer" + EVENT_DISTRIBUTING_PUSH_ENTRY = "push-entry" + EVENT_LEARNING_CREATE_METRIC = "create-metric" +) + +type EventManager struct { + events map[string]interface {} +} + +func (this *EventManager) On(name string, callback func(event *Event)) { + this.events[name] = callback +} + +func (this *EventManager) Off(event string) { + delete(this.events, event) +} + +func (this *EventManager) Trigger(name string, data interface{}) { + e := NewEvent(data) + for _, i := range (this.events) { + i.(func(*Event))(e) + } +} + +func (this *EventManager) GetEventsMap() map[string]interface{} { + return map[string]interface{}{ + EVENT_STORING: []string{EVENT_STORING_CREATE_ENTRY}, + EVENT_PROCESSING: []string{EVENT_PROCESSING_FEED_MAINTAINER}, + EVENT_DISTRIBUTING: []string{EVENT_DISTRIBUTING_PUSH_ENTRY}, + EVENT_LEARNING: []string{EVENT_LEARNING_CREATE_METRIC}, + } +} + +func NewEventManager() *EventManager { + e := make(map[string]interface{}) + return &EventManager{e} +} diff --git a/install.sh b/install.sh index f2bc913..5d53538 100755 --- a/install.sh +++ b/install.sh @@ -40,3 +40,6 @@ go get github.com/bradfitz/gomemcache/memcache # websoscket go get github.com/gorilla/websocket + +# sseserver +go get github.com/mroth/sseserver diff --git a/plugin/compiler/js.go b/plugin/compiler/js.go new file mode 100644 index 0000000..f266cec --- /dev/null +++ b/plugin/compiler/js.go @@ -0,0 +1,5 @@ +package compiler + +func getJsVM() {} + +func init() {} diff --git a/plugin/crawler.go b/plugin/crawler.go new file mode 100644 index 0000000..0de1c04 --- /dev/null +++ b/plugin/crawler.go @@ -0,0 +1,5 @@ +package plugin + +type Crawler interface { + +} diff --git a/plugin/indexer.go b/plugin/indexer.go new file mode 100644 index 0000000..136adec --- /dev/null +++ b/plugin/indexer.go @@ -0,0 +1,5 @@ +package plugin + +type Indexer interface { + +} diff --git a/plugin/manager.go b/plugin/manager.go new file mode 100644 index 0000000..61eb6f1 --- /dev/null +++ b/plugin/manager.go @@ -0,0 +1,41 @@ +package plugin + +type PluginManager struct { + Indexers map[string]interface{} + Crawlers map[string]interface{} + Sensors map[string]interface{} + Pipelines map[string]interface{} + Scenarios map[string]interface{} + Helpers map[string]interface{} + + api *ResourceApi +} + +func (this *PluginManager) GetResourceApi() interface{} { + return this.api +} + +func (this *PluginManager) InitIndexer(name string, profiler *Profiler) *Plugin { + p := NewPlugin(this, this.api, profiler) + + p.Init() + this.Indexers[name] = p + + return p +} + +func (this *PluginManager) FindPlugin(name string, profiler *Profiler) *interface{} { + return nil +} + +func (this *PluginManager) ExecPlugin(p Plugin) { + // profiler := p.profiler +} + +func NewPluginManager(resourceManager interface{}) *PluginManager { + pm := &PluginManager{} + + pm.api = NewResourceApi(resourceManager) + + return pm +} diff --git a/plugin/math/rpc_client.go b/plugin/math/rpc_client.go new file mode 100644 index 0000000..418e15b --- /dev/null +++ b/plugin/math/rpc_client.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "net/rpc" + "log" +) + +type Args struct { + X, Y int +} + +func main(){ + + client, err := rpc.Dial("tcp", "127.0.0.1:1234") + if err != nil { + log.Fatal("dialing:", err) + } + // Synchronous call + args := &Args{1,2} + var reply int + err = client.Call("bbb.Add", args, &reply) + if err != nil { + log.Fatal("arith error:", err) + } + fmt.Printf("Result: %d+%d=%d", args.X, args.Y, reply) +} diff --git a/plugin/pipeline.go b/plugin/pipeline.go new file mode 100644 index 0000000..50fd273 --- /dev/null +++ b/plugin/pipeline.go @@ -0,0 +1,5 @@ +package plugin + +type Pipeline interface { + +} diff --git a/plugin/pipeline/ai_neural_network.go b/plugin/pipeline/ai_neural_network.go new file mode 100644 index 0000000..0fc612c --- /dev/null +++ b/plugin/pipeline/ai_neural_network.go @@ -0,0 +1,11 @@ +package pipeline + +func AINeuralNetworkAnimator(data interface{}, viewerBrain interface{}) interface{} { + + // PIPELINE data through the Neural Netowrk + // of current connected Viewer/Audience + + return data +} + +func init() {} diff --git a/plugin/pipeline/pipeline.go b/plugin/pipeline/pipeline.go new file mode 100644 index 0000000..b4ef5ec --- /dev/null +++ b/plugin/pipeline/pipeline.go @@ -0,0 +1,8 @@ +package pipeline + +func Filter(data interface{}) interface{} { + // should call plugins of type PIPELINE + return RandomAnimator(data) +} + +func init() {} diff --git a/plugin/pipeline/random_animator.go b/plugin/pipeline/random_animator.go new file mode 100644 index 0000000..0b0a892 --- /dev/null +++ b/plugin/pipeline/random_animator.go @@ -0,0 +1,18 @@ +package pipeline + +import ( + "time" + "math/rand" +) + +func RandomAnimator(data interface {}) interface {} { + + // PIPE DELAY SIMULATION + + amt := time.Duration(rand.Intn(200)) + time.Sleep(amt * time.Millisecond) + + return data +} + +func init() {} diff --git a/plugin/plugin.go b/plugin/plugin.go new file mode 100644 index 0000000..7edbbbe --- /dev/null +++ b/plugin/plugin.go @@ -0,0 +1,15 @@ +package plugin + +type Plugin struct { + pluginManager *PluginManager + resourceApi *ResourceApi + + profiler *Profiler + rpcAddress interface{} +} + +func (this *Plugin) Init() {} + +func NewPlugin(pm *PluginManager, api *ResourceApi, profiler *Profiler) *Plugin { + return &Plugin{pm, api, profiler, ""} +} diff --git a/plugin/profiler.go b/plugin/profiler.go new file mode 100644 index 0000000..fe2014a --- /dev/null +++ b/plugin/profiler.go @@ -0,0 +1,9 @@ +package plugin + +type Profiler struct { + data map[string]string +} + +func NewProfiler(data map[string]string) *Profiler { + return &Profiler{data} +} diff --git a/plugin/provider/sensors.go b/plugin/provider/sensors.go new file mode 100644 index 0000000..3a235ab --- /dev/null +++ b/plugin/provider/sensors.go @@ -0,0 +1,5 @@ +package provider + +func Weather() {} +func StockIndices() {} +func SunActivity() {} diff --git a/plugin/resource_api.go b/plugin/resource_api.go new file mode 100644 index 0000000..42edc67 --- /dev/null +++ b/plugin/resource_api.go @@ -0,0 +1,19 @@ +package plugin + +type ResourceApi struct { + resourceManager interface{} +} + +func (this *ResourceApi) CreateEntryMetric() {} +func (this *ResourceApi) DeleteEntryMetric() {} +func (this *ResourceApi) UpdateEntryMetric() {} + +func (this *ResourceApi) ClearFeed() {} +func (this *ResourceApi) ReorderFeed() {} +func (this *ResourceApi) CreateFeedEntry() {} +func (this *ResourceApi) DeleteFeedEntry() {} +func (this *ResourceApi) UpdateFeedEntry() {} + +func NewResourceApi(resourceManager interface{}) *ResourceApi { + return &ResourceApi{resourceManager} +} diff --git a/plugin/scenario.go b/plugin/scenario.go new file mode 100644 index 0000000..8a1e75d --- /dev/null +++ b/plugin/scenario.go @@ -0,0 +1,5 @@ +package plugin + +type Scenario interface { + +} diff --git a/plugin/scenario/ai_neural_network_trainer.go b/plugin/scenario/ai_neural_network_trainer.go new file mode 100644 index 0000000..e1b1d3f --- /dev/null +++ b/plugin/scenario/ai_neural_network_trainer.go @@ -0,0 +1,17 @@ +package scenario + +import ( + _ "github.com/feedlabs/elasticfeed/elasticfeed/ai" +) + +func AINeuralNetworkTrainer(data interface{}, viewerBrain interface{}) interface{} { + + //----------------------------------------------------- + // Train ViewerBrain using Viewer specific metrics from + // front-end behaviours + //----------------------------------------------------- + + return viewerBrain +} + +func init() {} diff --git a/plugin/sensor.go b/plugin/sensor.go new file mode 100644 index 0000000..d53da75 --- /dev/null +++ b/plugin/sensor.go @@ -0,0 +1,5 @@ +package plugin + +type Sensor interface { + +} diff --git a/public/lp.html b/public/lp.html new file mode 100644 index 0000000..7c6fa97 --- /dev/null +++ b/public/lp.html @@ -0,0 +1,81 @@ + + + + + +
+
+
+
+
+
+ + diff --git a/public/ws.html b/public/ws.html new file mode 100644 index 0000000..cec0522 --- /dev/null +++ b/public/ws.html @@ -0,0 +1,85 @@ + + + + + +
+
+
+
+
+
+ + diff --git a/resource/admin.go b/resource/admin.go index c3f2f1b..7c68e66 100644 --- a/resource/admin.go +++ b/resource/admin.go @@ -5,7 +5,7 @@ import ( "strconv" "github.com/feedlabs/feedify/graph" - "github.com/feedlabs/elasticfeed/helper/config" + "github.com/feedlabs/elasticfeed/common/config" ) func (this *Admin) IsSuperUser() bool { @@ -136,8 +136,14 @@ func DeleteAdmin(id string) (error) { } func FindAdminByUsername(username string) (admin *Admin, err error) { - org := &Org{"26", "", "", 0, 0, 0} - whitelist := []string{"127.0.0.1", "192.168.1.51"} + + /* ----------------------------------------------- + * Hardcoded admin ORG ID + * ------------------------------------------------ + */ + + org := &Org{"0", "", "", 0, 0, 0} + whitelist := []string{"127.0.0.1", "192.168.1.51", "localhost"} password := "hello" if username == config.GetApiSuperuser() { @@ -146,7 +152,3 @@ func FindAdminByUsername(username string) (admin *Admin, err error) { return &Admin{"0", org, username, true, whitelist, password, 0}, nil } - -func init() { - Admins = make(map[string]*Admin) -} diff --git a/resource/application.go b/resource/application.go index 9561e30..80ff12a 100644 --- a/resource/application.go +++ b/resource/application.go @@ -107,7 +107,3 @@ func DeleteApplication(id string) (error) { _id, _ := strconv.Atoi(id) return storage.DeleteNode(_id) } - -func init() { - Applications = make(map[string]*Application) -} diff --git a/resource/feed.go b/resource/feed.go index 632b00d..bad14cd 100644 --- a/resource/feed.go +++ b/resource/feed.go @@ -7,6 +7,7 @@ import ( "github.com/feedlabs/feedify/graph" "github.com/feedlabs/elasticfeed/service/stream/controller/room" + "github.com/feedlabs/elasticfeed/workflow" ) func (this *Feed) AddEntry(entry Entry) (EntryId string, err error) { @@ -17,6 +18,22 @@ func (this *Feed) GetEntryList() (entries []*Entry, err error) { return GetEntryList(this.Id, this.Application.Id, this.Application.Org.Id) } +func (this *Feed) SetWorkflowfile(data map[string]interface{}) { + this.Workflowfile = data +} + +func (this *Feed) GetWorkflowfile() map[string]interface{} { + return this.Workflowfile +} + +func (this *Feed) InitWorkflow(wm *workflow.WorkflowManager) { + this.Workflow = wm.CreateFeedWorkflow(this, this.GetWorkflowfile()) +} + +func (this *Feed) GetWorkflow() *workflow.Workflow { + return this.Workflow +} + func GetFeedList(ApplicationId string, OrgId string) (feedList []*Feed, err error) { app, err := GetApplication(ApplicationId, OrgId) if err != nil { @@ -33,7 +50,7 @@ func GetFeedList(ApplicationId string, OrgId string) (feedList []*Feed, err erro id := strconv.Itoa(rel.EndNode.Id) rels, _ := storage.RelationshipsNode(rel.EndNode.Id, "entry") - feed := &Feed{id , app, data, len(rels)} + feed := NewFeed(id , app, data, len(rels)) feeds = append(feeds, feed) } @@ -60,7 +77,7 @@ func GetFeed(id string, applicationId string, orgId string) (feed *Feed, err err if node != nil && Contains(node.Labels, RESOURCE_FEED_LABEL) && app.Id == node.Data["applicationId"].(string) { data := node.Data["data"].(string) rels, _ := storage.RelationshipsNode(node.Id, "entry") - return &Feed{strconv.Itoa(node.Id), app, data, len(rels)}, nil + return NewFeed(strconv.Itoa(node.Id), app, data, len(rels)), nil } return nil, errors.New("FeedId not exist for ApplicationId `"+applicationId+"`") @@ -112,6 +129,6 @@ func ActionEmptyFeed(id string) { room.Publish <- room.NewFeedEvent(room.FEED_EMPTY, id, "empty") } -func init() { - Feeds = make(map[string]*Feed) +func NewFeed(id string, app *Application, data string, entries int) *Feed { + return &Feed{id, app, data, entries, nil, nil} } diff --git a/resource/manager.go b/resource/manager.go new file mode 100644 index 0000000..236ef2f --- /dev/null +++ b/resource/manager.go @@ -0,0 +1,13 @@ +package resource + +type ResourceManager struct {} + +func (this * ResourceManager) Init() { + InitStorage() + InitResources() + InitStreamCommunicator() +} + +func NewResourceManager() *ResourceManager { + return &ResourceManager{} +} diff --git a/resource/metric.go b/resource/metric.go new file mode 100644 index 0000000..8f6efbf --- /dev/null +++ b/resource/metric.go @@ -0,0 +1,5 @@ +package resource + +func (this *Metric) NewMetric() *Metric { + return &Metric{} +} diff --git a/resource/resource.go b/resource/resource.go index d2adb3d..f075123 100644 --- a/resource/resource.go +++ b/resource/resource.go @@ -2,26 +2,41 @@ package resource import ( "errors" + "encoding/json" + "time" + "math/rand" "github.com/feedlabs/feedify/service" "github.com/feedlabs/feedify/graph" "github.com/feedlabs/feedify/stream" + + "github.com/feedlabs/elasticfeed/service/stream/controller/room" + "github.com/feedlabs/elasticfeed/service/stream/model" + "github.com/feedlabs/elasticfeed/plugin/pipeline" + + "github.com/feedlabs/elasticfeed/workflow" ) -const RESOURCE_ORG_LABEL = "org" -const RESOURCE_ADMIN_LABEL = "admin" -const RESOURCE_TOKEN_LABEL = "token" -const RESOURCE_APPLICATION_LABEL = "application" -const RESOURCE_FEED_LABEL = "feed" -const RESOURCE_ENTRY_LABEL = "entry" +const ( + RESOURCE_ORG_LABEL = "org" + RESOURCE_ADMIN_LABEL = "admin" + RESOURCE_TOKEN_LABEL = "token" + RESOURCE_APPLICATION_LABEL = "application" + RESOURCE_FEED_LABEL = "feed" + RESOURCE_ENTRY_LABEL = "entry" + RESOURCE_METRIC_LABEL = "metric" + RESOURCE_VIEWER_LABEL = "viewer" +) var ( Orgs map[string]*Org - Admins map[string]*Admin - Tokens map[string]*Token + Admins map[string]*Admin + Tokens map[string]*Token Applications map[string]*Application - Feeds map[string]*Feed - Entries map[string]*Entry + Feeds map[string]*Feed + Entries map[string]*Entry + Metrics map[string]*Metric + Viewers map[string]*Viewer message *stream.StreamMessage storage *graph.GraphStorage @@ -68,6 +83,9 @@ type Feed struct { Data string Entries int + + Workflow *workflow.Workflow + Workflowfile map[string]interface{} } type Entry struct { @@ -76,18 +94,104 @@ type Entry struct { Data string } -func init() { - stream_service, _ := service.NewStream() - if stream_service == nil { - panic(errors.New("Cannot create stream service")) +type Viewer struct {} + +type Metric struct {} + +func ResourceStreamManager() { + for { + select { + case socketEvent := <-room.ResourceEvent: + + go ResourceStreamRequest(socketEvent) + } } - message = stream_service.Message +} + +func ResourceStreamRequest(socketEvent model.SocketEvent) { + + // ******************************************************************* + // here should be implemented REAL CONTENT IMPROVEMENT + // based on connected user (viewer) or users (audience)!: habits, behaviours, stats etc. + // PIPE: filtering, customization + // SCENARIO-ENGINE: scenarios + // ******************************************************************* + + // ******************************************************************* + // SCENARIO AND RULES/METRICS + // should use go routine with time limit to query filter rules + // if in specific time there is no rules the results should be sent + // client feed. After this the next package should be sent with + // rules which entries should be remove/hidden from the view! + // ******************************************************************* + + timeout := make(chan bool, 1) + results := make(chan []*Entry, 1) + + list, _ := GetEntryList(socketEvent.FeedId, socketEvent.AppId, socketEvent.OrgId) + + // PIPE TIMEOUT + go func() { + amt := time.Duration(rand.Intn(100)) + time.Sleep(amt * time.Millisecond) + timeout <- true + }() + + // SHOULD BE A FILTER IMPLEMENTATION + go func(list []*Entry, socketEvent model.SocketEvent) { + list = pipeline.Filter(list).([]*Entry) + results <- list + }(list, socketEvent) + + select { + + // IF PIPE TAKES TOO MUCH TIME, DATA DELAYED + case <-timeout: + + event := room.NewFeedEvent(room.FEED_ENTRY_NEW, socketEvent.FeedId, "{Content:\"tiemout\"}") + data, _ := json.Marshal(event) + + if socketEvent.Ws != nil { + amt := time.Duration(rand.Intn(500)) * 1000 + time.Sleep(amt * time.Microsecond) + socketEvent.Ws.WriteMessage(1, data) + } + + if socketEvent.Ch != nil { + socketEvent.Ch <- data + } + + // IF DATA ARRIVES WITHOUT DELAY + case list := <-results: + + // ********************************************************************* + // register socket handler + // needs to send notiffication to long pooling + ws + // join should generate uniqe ID and client should use it + // maybe sessionID could be as uniqeID ? + // room.FeedSubscribers[socketEvent.FeedId][channelID] = socketEvent + // ********************************************************************* + // + // timeout with channels and routines? + // http://blog.golang.org/go-concurrency-patterns-timing-out-and + + // amt := time.Duration(rand.Intn(500)) * 10000 + // time.Sleep(amt * time.Microsecond) + + d, _ := json.Marshal(list) + event := room.NewFeedEvent(room.FEED_ENTRY_INIT, socketEvent.FeedId, string(d)) + data, _ := json.Marshal(event) + + if socketEvent.Ws != nil { + socketEvent.Ws.WriteMessage(1, data) + } + + if socketEvent.Ch != nil { + socketEvent.Ch <- data + } - graph_service, _ := service.NewGraph() - if graph_service == nil { - panic(errors.New("Cannot create graph service")) } - storage = graph_service.Storage + } func Contains(s []string, e string) bool { @@ -103,3 +207,26 @@ func ConvertInterfaceToStringArray(d interface{}) []string { } return output } + +func InitResources() { + Admins = make(map[string]*Admin) + Applications = make(map[string]*Application) + Feeds = make(map[string]*Feed) + Entries = make(map[string]*Entry) + Orgs = make(map[string]*Org) + Tokens = make(map[string]*Token) + Metrics = make(map[string]*Metric) + Viewers = make(map[string]*Viewer) +} + +func InitStorage() { + graph_service, _ := service.NewGraph() + if graph_service == nil { + panic(errors.New("Cannot create graph service")) + } + storage = graph_service.Storage +} + +func InitStreamCommunicator() { + go ResourceStreamManager() +} diff --git a/resource/token.go b/resource/token.go index 4345e84..135d3ed 100644 --- a/resource/token.go +++ b/resource/token.go @@ -7,10 +7,6 @@ import ( "github.com/feedlabs/feedify/graph" ) -func init() { - Tokens = make(map[string]*Token) -} - func GetTokenList(AdminId string, OrgId string) (tokenLinst []*Token, err error) { admin, err := GetAdmin(AdminId, OrgId) if err != nil { diff --git a/resource/viewer.go b/resource/viewer.go new file mode 100644 index 0000000..958e354 --- /dev/null +++ b/resource/viewer.go @@ -0,0 +1 @@ +package resource diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..c1d3587 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,80 @@ +#!/bin/bash +# +# This script builds the application from source for multiple platforms. +set -e + +# Get the parent directory of where this script is. +SOURCE="${BASH_SOURCE[0]}" +while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done +DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )" + +# Change into that directory +cd $DIR + +# Get the git commit +GIT_COMMIT=$(git rev-parse HEAD) +GIT_DIRTY=$(test -n "`git status --porcelain`" && echo "+CHANGES" || true) + +# If its dev mode, only build for ourself +if [ "${TF_DEV}x" != "x" ]; then + XC_OS=${XC_OS:-$(go env GOOS)} + XC_ARCH=${XC_ARCH:-$(go env GOARCH)} +fi + +# Determine the arch/os combos we're building for +XC_ARCH=${XC_ARCH:-"386 amd64 arm"} +XC_OS=${XC_OS:-linux darwin windows freebsd openbsd} + +# Install dependencies +echo "==> Getting dependencies..." + +#go get ./... + +# Delete the old dir +echo "==> Removing old directory..." +rm -f bin/* +rm -rf pkg/* +mkdir -p bin/ + +# Build! +echo "==> Building..." +set +e +gox \ + -os="${XC_OS}" \ + -arch="${XC_ARCH}" \ + -ldflags "-X main.GitCommit ${GIT_COMMIT}${GIT_DIRTY}" \ + -output "pkg/{{.OS}}_{{.Arch}}/elasticfeed-{{.Dir}}" \ + ./... +set -e + +# Make sure "elasticfeed-elasticfeed" is renamed properly +for PLATFORM in $(find ./pkg -mindepth 1 -maxdepth 1 -type d); do + set +e + mv ${PLATFORM}/elasticfeed-elasticfeed.exe ${PLATFORM}/elasticfeed.exe 2>/dev/null + mv ${PLATFORM}/elasticfeed-elasticfeed ${PLATFORM}/elasticfeed 2>/dev/null + set -e +done + +# Move all the compiled things to the $GOPATH/bin +GOPATH=${GOPATH:-$(go env GOPATH)} +case $(uname) in + CYGWIN*) + GOPATH="$(cygpath $GOPATH)" + ;; +esac +OLDIFS=$IFS +IFS=: MAIN_GOPATH=($GOPATH) +IFS=$OLDIFS + +# Copy our OS/Arch to the bin/ directory +echo "==> Copying binaries for this platform..." +DEV_PLATFORM="./pkg/$(go env GOOS)_$(go env GOARCH)" +for F in $(find ${DEV_PLATFORM} -mindepth 1 -maxdepth 1 -type f); do + cp ${F} bin/ + cp ${F} ${MAIN_GOPATH}/bin/ +done + +# Done! +echo +echo "==> Results:" +ls -hl bin/ diff --git a/scripts/dist.sh b/scripts/dist.sh new file mode 100755 index 0000000..2e76ed0 --- /dev/null +++ b/scripts/dist.sh @@ -0,0 +1,57 @@ +#!/bin/bash +set -e + +# Get the parent directory of where this script is. +SOURCE="${BASH_SOURCE[0]}" +while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done +DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )" + +# Change into that dir because we expect that +cd $DIR + +# Get the version from the command line +VERSION=$1 +if [ -z $VERSION ]; then + echo "Please specify a version." + exit 1 +fi + +# Make sure we have a bintray API key +if [ -z $BINTRAY_API_KEY ]; then + echo "Please set your bintray API key in the BINTRAY_API_KEY env var." + exit 1 +fi + +# Zip and copy to the dist dir +echo "==> Packaging..." +rm -rf ./pkg/dist +mkdir -p ./pkg/dist +for PLATFORM in $(find ./pkg -mindepth 1 -maxdepth 1 -type d); do + OSARCH=$(basename ${PLATFORM}) + + if [ $OSARCH = "dist" ]; then + continue + fi + + echo "--> ${OSARCH}" + pushd $PLATFORM >/dev/null 2>&1 + zip ../dist/elasticfeed_${VERSION}_${OSARCH}.zip ./* + popd >/dev/null 2>&1 +done + +# Make the checksums +echo "==> Checksumming..." +pushd ./pkg/dist >/dev/null 2>&1 +shasum -a256 * > ./elasticfeed_${VERSION}_SHA256SUMS +popd >/dev/null 2>&1 + +echo "==> Uploading..." +for ARCHIVE in ./pkg/dist/*; do + ARCHIVE_NAME=$(basename ${ARCHIVE}) + + echo Uploading: $ARCHIVE_NAME + curl \ + -T ${ARCHIVE} \ + -umitchellh:${BINTRAY_API_KEY} \ + "https://api.bintray.com/content/kris-lab/elasticfeed/elasticfeed/${VERSION}/${ARCHIVE_NAME}" +done diff --git a/scripts/upload.sh b/scripts/upload.sh new file mode 100755 index 0000000..f8a23b7 --- /dev/null +++ b/scripts/upload.sh @@ -0,0 +1,33 @@ +#!/bin/bash +set -e + +# Get the parent directory of where this script is. +SOURCE="${BASH_SOURCE[0]}" +while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done +DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )" + +# Change into that dir because we expect that +cd $DIR + +# Get the version from the command line +VERSION=$1 +if [ -z $VERSION ]; then + echo "Please specify a version." + exit 1 +fi + +# Make sure we have a bintray API key +if [ -z $BINTRAY_API_KEY ]; then + echo "Please set your bintray API key in the BINTRAY_API_KEY env var." + exit 1 +fi + +for ARCHIVE in ./pkg/dist/*; do + ARCHIVE_NAME=$(basename ${ARCHIVE}) + + echo Uploading: $ARCHIVE_NAME + curl \ + -T ${ARCHIVE} \ + -umitchellh:${BINTRAY_API_KEY} \ + "https://api.bintray.com/content/kris-lab/elasticfeed/elasticfeed/${VERSION}/${ARCHIVE_NAME}" +done diff --git a/scripts/website_push.sh b/scripts/website_push.sh new file mode 100755 index 0000000..fafcbd7 --- /dev/null +++ b/scripts/website_push.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Get the parent directory of where this script is. +SOURCE="${BASH_SOURCE[0]}" +while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done +DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )" + +# Change into that directory +cd $DIR + +# Push the subtree (force) +git push heroku `git subtree split --prefix website master`:master --force diff --git a/service/db/api.go b/service/db/api.go deleted file mode 100644 index c51e66e..0000000 --- a/service/db/api.go +++ /dev/null @@ -1,7 +0,0 @@ -package api - -import ( - _ "github.com/feedlabs/elasticfeed/service/db/v1" -) - -func init() {} diff --git a/service/db/v1/api.go b/service/db/v1/api.go deleted file mode 100644 index 522b251..0000000 --- a/service/db/v1/api.go +++ /dev/null @@ -1,8 +0,0 @@ -package v1 - -import ( - _ "github.com/feedlabs/elasticfeed/service/db/v1/router" - _ "github.com/feedlabs/elasticfeed/service/db/v1/controller" -) - -func init() {} diff --git a/service/db/v1/router/default.go b/service/db/v1/router/default.go deleted file mode 100644 index 82db7f3..0000000 --- a/service/db/v1/router/default.go +++ /dev/null @@ -1,11 +0,0 @@ -package router - -import ( - "github.com/feedlabs/feedify" - "github.com/feedlabs/elasticfeed/service/db/v1/controller" -) - -func init() { - feedify.Router("/", &controller.DefaultController{}, "get:Get") - feedify.Router("/v1", &controller.DefaultController{}, "get:Get") -} diff --git a/service/service.go b/service/service.go index 3e3d97e..02da517 100644 --- a/service/service.go +++ b/service/service.go @@ -1,9 +1,37 @@ package service import ( - _ "github.com/feedlabs/elasticfeed/service/db" - _ "github.com/feedlabs/elasticfeed/service/stream" - _ "github.com/feedlabs/elasticfeed/service/system" + "github.com/feedlabs/elasticfeed/service/store" + "github.com/feedlabs/elasticfeed/service/stream" + "github.com/feedlabs/elasticfeed/service/system" ) -func init() {} +type Service struct {} + +type ServiceManager struct { + store *store.DbService + stream *stream.StreamService + system *system.SystemService +} + +func (this *ServiceManager) Init() { + this.system.Init() + this.stream.Init() + this.store.Init() +} + +func (this *ServiceManager) GetDbService() *store.DbService { + return this.store +} + +func (this *ServiceManager) GetStreamService() *stream.StreamService { + return this.stream +} + +func (this *ServiceManager) GetSystemService() *system.SystemService { + return this.system +} + +func NewServiceManager() *ServiceManager { + return &ServiceManager{} +} diff --git a/service/store/api.go b/service/store/api.go new file mode 100644 index 0000000..ff25023 --- /dev/null +++ b/service/store/api.go @@ -0,0 +1,17 @@ +package store + +import ( + "github.com/feedlabs/elasticfeed/service/store/v1/router" + "github.com/feedlabs/elasticfeed/service/store/v1/controller" +) + +type DbService struct {} + +func (this *DbService) Init() { + router.InitRouters() + controller.InitService() +} + +func NewMetricService() *DbService { + return &DbService{} +} diff --git a/service/db/v1/controller/admin.go b/service/store/v1/controller/admin.go similarity index 97% rename from service/db/v1/controller/admin.go rename to service/store/v1/controller/admin.go index 71807de..0ba1fdb 100644 --- a/service/db/v1/controller/admin.go +++ b/service/store/v1/controller/admin.go @@ -4,7 +4,7 @@ import ( "encoding/json" "github.com/feedlabs/elasticfeed/resource" - "github.com/feedlabs/elasticfeed/service/db/v1/template/admin" + "github.com/feedlabs/elasticfeed/service/store/v1/template/admin" ) type AdminController struct { diff --git a/service/db/v1/controller/application.go b/service/store/v1/controller/application.go similarity index 98% rename from service/db/v1/controller/application.go rename to service/store/v1/controller/application.go index 63c1bd0..2304431 100644 --- a/service/db/v1/controller/application.go +++ b/service/store/v1/controller/application.go @@ -4,7 +4,7 @@ import ( "encoding/json" "github.com/feedlabs/elasticfeed/resource" - "github.com/feedlabs/elasticfeed/service/db/v1/template/application" + "github.com/feedlabs/elasticfeed/service/store/v1/template/application" ) type ApplicationController struct { diff --git a/service/db/v1/controller/default.go b/service/store/v1/controller/default.go similarity index 90% rename from service/db/v1/controller/default.go rename to service/store/v1/controller/default.go index 63a464a..db36df9 100644 --- a/service/db/v1/controller/default.go +++ b/service/store/v1/controller/default.go @@ -6,8 +6,8 @@ import ( "github.com/feedlabs/feedify" "github.com/feedlabs/elasticfeed/resource" - "github.com/feedlabs/elasticfeed/helper" - "github.com/feedlabs/elasticfeed/service/db/v1/template" + "github.com/feedlabs/elasticfeed/common" + "github.com/feedlabs/elasticfeed/service/store/v1/template" ) type DefaultController struct { @@ -53,7 +53,7 @@ func SetGlobalResponseHeader() { func SetAuthenticationFilter() { var AuthUser = func(ctx *context.Context) { - ctx.Input.Data["admin"] = helper.Auth(ctx) + ctx.Input.Data["admin"] = common.Auth(ctx) } beego.InsertFilter("/v1/*", beego.BeforeRouter, AuthUser) } @@ -67,7 +67,7 @@ func NoRoutes() { beego.InsertFilter("/*", beego.AfterStatic, router) } -func init() { +func InitService() { SetAuthenticationFilter() SetGlobalResponseHeader() // NoRoutes() diff --git a/service/db/v1/controller/entry.go b/service/store/v1/controller/entry.go similarity index 98% rename from service/db/v1/controller/entry.go rename to service/store/v1/controller/entry.go index 8bc6a24..7bb3772 100644 --- a/service/db/v1/controller/entry.go +++ b/service/store/v1/controller/entry.go @@ -4,7 +4,7 @@ import ( "encoding/json" "github.com/feedlabs/elasticfeed/resource" - "github.com/feedlabs/elasticfeed/service/db/v1/template/entry" + "github.com/feedlabs/elasticfeed/service/store/v1/template/entry" ) type EntryController struct { diff --git a/service/db/v1/controller/feed.go b/service/store/v1/controller/feed.go similarity index 96% rename from service/db/v1/controller/feed.go rename to service/store/v1/controller/feed.go index e421435..99ff1e1 100644 --- a/service/db/v1/controller/feed.go +++ b/service/store/v1/controller/feed.go @@ -4,9 +4,9 @@ import ( "encoding/json" "github.com/feedlabs/elasticfeed/resource" - "github.com/feedlabs/elasticfeed/service/db/v1/template/feed" - "github.com/feedlabs/elasticfeed/service/db/v1/template" + "github.com/feedlabs/elasticfeed/service/store/v1/template" + "github.com/feedlabs/elasticfeed/service/store/v1/template/feed" ) type FeedController struct { diff --git a/service/db/v1/controller/org.go b/service/store/v1/controller/org.go similarity index 97% rename from service/db/v1/controller/org.go rename to service/store/v1/controller/org.go index c26211a..f18ed78 100644 --- a/service/db/v1/controller/org.go +++ b/service/store/v1/controller/org.go @@ -4,7 +4,7 @@ import ( "encoding/json" "github.com/feedlabs/elasticfeed/resource" - template "github.com/feedlabs/elasticfeed/service/db/v1/template/org" + template "github.com/feedlabs/elasticfeed/service/store/v1/template/org" ) type OrgController struct { diff --git a/service/db/v1/controller/token.go b/service/store/v1/controller/token.go similarity index 98% rename from service/db/v1/controller/token.go rename to service/store/v1/controller/token.go index 4b7cad6..c7a0324 100644 --- a/service/db/v1/controller/token.go +++ b/service/store/v1/controller/token.go @@ -4,7 +4,7 @@ import ( "encoding/json" "github.com/feedlabs/elasticfeed/resource" - "github.com/feedlabs/elasticfeed/service/db/v1/template/token" + "github.com/feedlabs/elasticfeed/service/store/v1/template/token" ) type TokenController struct { diff --git a/service/db/v1/router/admin.go b/service/store/v1/router/admin.go similarity index 74% rename from service/db/v1/router/admin.go rename to service/store/v1/router/admin.go index 47bfbd8..19198bf 100644 --- a/service/db/v1/router/admin.go +++ b/service/store/v1/router/admin.go @@ -2,10 +2,10 @@ package router import ( "github.com/feedlabs/feedify" - "github.com/feedlabs/elasticfeed/service/db/v1/controller" + "github.com/feedlabs/elasticfeed/service/store/v1/controller" ) -func init() { +func InitAdminRouters() { feedify.Router("/v1/admin", &controller.AdminController{}, "get:GetList;post:Post") feedify.Router("/v1/admin/:adminId:string", &controller.AdminController{}, "get:Get;delete:Delete;put:Put") } diff --git a/service/db/v1/router/application.go b/service/store/v1/router/application.go similarity index 75% rename from service/db/v1/router/application.go rename to service/store/v1/router/application.go index 4a72333..2f5a634 100644 --- a/service/db/v1/router/application.go +++ b/service/store/v1/router/application.go @@ -2,10 +2,10 @@ package router import ( "github.com/feedlabs/feedify" - "github.com/feedlabs/elasticfeed/service/db/v1/controller" + "github.com/feedlabs/elasticfeed/service/store/v1/controller" ) -func init() { +func InitApplicationRouters() { feedify.Router("/v1/application", &controller.ApplicationController{}, "get:GetList;post:Post") feedify.Router("/v1/application/:applicationId:string", &controller.ApplicationController{}, "get:Get;delete:Delete;put:Put") } diff --git a/service/store/v1/router/default.go b/service/store/v1/router/default.go new file mode 100644 index 0000000..eb0b3ca --- /dev/null +++ b/service/store/v1/router/default.go @@ -0,0 +1,21 @@ +package router + +import ( + "github.com/feedlabs/feedify" + "github.com/feedlabs/elasticfeed/service/store/v1/controller" +) + +func InitDefaultRouters() { + feedify.Router("/", &controller.DefaultController{}, "get:Get") + feedify.Router("/v1", &controller.DefaultController{}, "get:Get") +} + +func InitRouters() { + InitDefaultRouters() + InitAdminRouters() + InitApplicationRouters() + InitEntryRouters() + InitFeedRouters() + InitOrgRouters() + InitTokenRouters() +} diff --git a/service/db/v1/router/entry.go b/service/store/v1/router/entry.go similarity index 87% rename from service/db/v1/router/entry.go rename to service/store/v1/router/entry.go index dfa9104..44d1398 100644 --- a/service/db/v1/router/entry.go +++ b/service/store/v1/router/entry.go @@ -2,10 +2,10 @@ package router import ( "github.com/feedlabs/feedify" - "github.com/feedlabs/elasticfeed/service/db/v1/controller" + "github.com/feedlabs/elasticfeed/service/store/v1/controller" ) -func init() { +func InitEntryRouters() { feedify.Router("/v1/application/:applicationId:string/entry", &controller.EntryController{}, "post:Post") feedify.Router("/v1/application/:applicationId:string/entry/:feedEntryId:int", &controller.EntryController{}, "get:Get;delete:Delete;put:Put") diff --git a/service/db/v1/router/feed.go b/service/store/v1/router/feed.go similarity index 86% rename from service/db/v1/router/feed.go rename to service/store/v1/router/feed.go index bde8170..799624f 100644 --- a/service/db/v1/router/feed.go +++ b/service/store/v1/router/feed.go @@ -2,10 +2,10 @@ package router import ( "github.com/feedlabs/feedify" - "github.com/feedlabs/elasticfeed/service/db/v1/controller" + "github.com/feedlabs/elasticfeed/service/store/v1/controller" ) -func init() { +func InitFeedRouters() { feedify.Router("/v1/application/:applicationId:string/feed", &controller.FeedController{}, "get:GetList;post:Post") feedify.Router("/v1/application/:applicationId:string/feed/:feedId:int", &controller.FeedController{}, "get:Get;delete:Delete;put:Put") diff --git a/service/db/v1/router/org.go b/service/store/v1/router/org.go similarity index 73% rename from service/db/v1/router/org.go rename to service/store/v1/router/org.go index 6a6b08a..8e8a1eb 100644 --- a/service/db/v1/router/org.go +++ b/service/store/v1/router/org.go @@ -2,10 +2,10 @@ package router import ( "github.com/feedlabs/feedify" - "github.com/feedlabs/elasticfeed/service/db/v1/controller" + "github.com/feedlabs/elasticfeed/service/store/v1/controller" ) -func init() { +func InitOrgRouters() { feedify.Router("/v1/org", &controller.OrgController{}, "get:GetList;post:Post") feedify.Router("/v1/org/:orgId:string", &controller.OrgController{}, "get:Get;delete:Delete;put:Put") } diff --git a/service/db/v1/router/token.go b/service/store/v1/router/token.go similarity index 85% rename from service/db/v1/router/token.go rename to service/store/v1/router/token.go index f3be790..098aafd 100644 --- a/service/db/v1/router/token.go +++ b/service/store/v1/router/token.go @@ -2,10 +2,10 @@ package router import ( "github.com/feedlabs/feedify" - "github.com/feedlabs/elasticfeed/service/db/v1/controller" + "github.com/feedlabs/elasticfeed/service/store/v1/controller" ) -func init() { +func InitTokenRouters() { feedify.Router("/v1/org/:orgId:string/token", &controller.TokenController{}, "get:GetOrgList;post:PostOrg") feedify.Router("/v1/org/:orgId:string/token/:tokenId:string", &controller.TokenController{}, "get:GetOrg;delete:DeleteOrg") diff --git a/service/db/v1/template/admin/request.go b/service/store/v1/template/admin/request.go similarity index 100% rename from service/db/v1/template/admin/request.go rename to service/store/v1/template/admin/request.go diff --git a/service/db/v1/template/admin/response.go b/service/store/v1/template/admin/response.go similarity index 100% rename from service/db/v1/template/admin/response.go rename to service/store/v1/template/admin/response.go diff --git a/service/db/v1/template/application/request.go b/service/store/v1/template/application/request.go similarity index 100% rename from service/db/v1/template/application/request.go rename to service/store/v1/template/application/request.go diff --git a/service/db/v1/template/application/response.go b/service/store/v1/template/application/response.go similarity index 100% rename from service/db/v1/template/application/response.go rename to service/store/v1/template/application/response.go diff --git a/service/db/v1/template/const.go b/service/store/v1/template/const.go similarity index 100% rename from service/db/v1/template/const.go rename to service/store/v1/template/const.go diff --git a/service/db/v1/template/default.go b/service/store/v1/template/default.go similarity index 100% rename from service/db/v1/template/default.go rename to service/store/v1/template/default.go diff --git a/service/db/v1/template/entry/request.go b/service/store/v1/template/entry/request.go similarity index 100% rename from service/db/v1/template/entry/request.go rename to service/store/v1/template/entry/request.go diff --git a/service/db/v1/template/entry/response.go b/service/store/v1/template/entry/response.go similarity index 100% rename from service/db/v1/template/entry/response.go rename to service/store/v1/template/entry/response.go diff --git a/service/db/v1/template/feed/request.go b/service/store/v1/template/feed/request.go similarity index 100% rename from service/db/v1/template/feed/request.go rename to service/store/v1/template/feed/request.go diff --git a/service/db/v1/template/feed/response.go b/service/store/v1/template/feed/response.go similarity index 100% rename from service/db/v1/template/feed/response.go rename to service/store/v1/template/feed/response.go diff --git a/service/db/v1/template/org/request.go b/service/store/v1/template/org/request.go similarity index 96% rename from service/db/v1/template/org/request.go rename to service/store/v1/template/org/request.go index 0efc62b..b484e24 100644 --- a/service/db/v1/template/org/request.go +++ b/service/store/v1/template/org/request.go @@ -3,7 +3,7 @@ package org import ( "errors" "github.com/feedlabs/feedify/context" - "github.com/feedlabs/elasticfeed/service/db/v1/template" + "github.com/feedlabs/elasticfeed/service/store/v1/template" ) func CheckRequiredParams() { diff --git a/service/db/v1/template/org/response.go b/service/store/v1/template/org/response.go similarity index 98% rename from service/db/v1/template/org/response.go rename to service/store/v1/template/org/response.go index 3c951bf..37a7b0a 100644 --- a/service/db/v1/template/org/response.go +++ b/service/store/v1/template/org/response.go @@ -3,7 +3,7 @@ package org import ( "strconv" "github.com/feedlabs/elasticfeed/resource" - "github.com/feedlabs/elasticfeed/service/db/v1/template" + "github.com/feedlabs/elasticfeed/service/store/v1/template" "errors" "sort" ) diff --git a/service/db/v1/template/request.go b/service/store/v1/template/request.go similarity index 100% rename from service/db/v1/template/request.go rename to service/store/v1/template/request.go diff --git a/service/db/v1/template/response.go b/service/store/v1/template/response.go similarity index 100% rename from service/db/v1/template/response.go rename to service/store/v1/template/response.go diff --git a/service/db/v1/template/token/request.go b/service/store/v1/template/token/request.go similarity index 100% rename from service/db/v1/template/token/request.go rename to service/store/v1/template/token/request.go diff --git a/service/db/v1/template/token/response.go b/service/store/v1/template/token/response.go similarity index 100% rename from service/db/v1/template/token/response.go rename to service/store/v1/template/token/response.go diff --git a/service/stream/controller/channel/long_pooling.go b/service/stream/controller/channel/long_pooling.go index 8e2d838..0ea3a4f 100644 --- a/service/stream/controller/channel/long_pooling.go +++ b/service/stream/controller/channel/long_pooling.go @@ -23,6 +23,12 @@ func (this *LongPollingController) Join() { defer sess.SessionRelease(w) room.Join(chid, nil) + + list := make(map[string]interface {}) + list["response"] = room.NewChannelEvent(room.CHANNEL_JOIN, "system", "join") + + this.Data["json"] = list + this.ServeJson() } func (this *LongPollingController) Post() { @@ -37,7 +43,19 @@ func (this *LongPollingController) Post() { // should be executed and returned directly to user // lastReceived time should not be changed in that case - room.Publish <- room.NewSystemEvent(room.CHANNEL_MESSAGE, chid, data) + // room.Publish <- room.NewSystemEvent(room.CHANNEL_MESSAGE, chid, data) + + ch := make(chan []byte) + + room.ResourceEvent <- room.NewSocketEvent([]byte(data), nil, ch) + + response := <-ch + + list := make(map[string]string) + list["response"] = string(response) + + this.Data["json"] = list + this.ServeJson() } func (this *LongPollingController) Fetch() { diff --git a/service/stream/controller/channel/sse.go b/service/stream/controller/channel/sse.go new file mode 100644 index 0000000..17f57a8 --- /dev/null +++ b/service/stream/controller/channel/sse.go @@ -0,0 +1,17 @@ +package channel + +import ( + "github.com/feedlabs/feedify" + +// "github.com/mroth/sseserver" +) + +type SSEController struct { + feedify.Controller +} + +func (this *SSEController) Join() { +} + +func (this *SSEController) Post() { +} diff --git a/service/stream/controller/channel/websocket.go b/service/stream/controller/channel/websocket.go index 568cc00..c23ab62 100644 --- a/service/stream/controller/channel/websocket.go +++ b/service/stream/controller/channel/websocket.go @@ -2,6 +2,7 @@ package channel import ( "net/http" + "encoding/json" "github.com/feedlabs/feedify" "github.com/gorilla/websocket" @@ -36,13 +37,15 @@ func (this *WebSocketController) Join() { room.Join(chid, ws) defer room.Leave(chid) + data, _ := json.Marshal(room.NewChannelEvent(room.CHANNEL_JOIN, chid, "join")) + ws.WriteMessage(websocket.TextMessage, data) + for { _, p, err := ws.ReadMessage() if err != nil { return } - room.Publish <- room.NewSystemEvent(room.CHANNEL_MESSAGE, chid, string(p)) - room.P2P <- ws + room.ResourceEvent <- room.NewSocketEvent(p, ws, nil) } } diff --git a/service/stream/controller/controller.go b/service/stream/controller/controller.go index 86c04e6..533e71a 100644 --- a/service/stream/controller/controller.go +++ b/service/stream/controller/controller.go @@ -1,8 +1,9 @@ package controller import ( - _ "github.com/feedlabs/elasticfeed/service/stream/controller/channel" - _ "github.com/feedlabs/elasticfeed/service/stream/controller/room" + "github.com/feedlabs/elasticfeed/service/stream/controller/room" ) -func init() {} +func InitRooms() { + room.InitFeedRoom() +} diff --git a/service/stream/controller/room/feed.go b/service/stream/controller/room/feed.go index 2ef6d2e..c839d4b 100644 --- a/service/stream/controller/room/feed.go +++ b/service/stream/controller/room/feed.go @@ -8,7 +8,9 @@ import ( "github.com/feedlabs/feedify" "github.com/gorilla/websocket" + "github.com/astaxie/beego/session" + "github.com/feedlabs/elasticfeed/service/stream/model" ) @@ -19,18 +21,14 @@ const ( SYSTEM_FEED_MESSAGE = 1 - FEED_RELOAD = 1 - FEED_EMPTY = 2 - FEED_ENTRY_NEW = 3 - FEED_ENTRY_INIT = 4 - FEED_ENTRY_MORE = 5 - FEED_HIDE = 6 - FEED_SHOW = 7 - FEED_ENTRY_MESSAGE = 8 - FEED_AUTHENTICATED = 100 - FEED_AUTHENTICATION_REQUIRED = 101 - FEED_AUTHENTICATION_FAILED = 102 - FEED_LOGGED_OUT = 103 + FEED_RELOAD = 1 + FEED_EMPTY = 2 + FEED_ENTRY_NEW = 3 + FEED_ENTRY_INIT = 4 + FEED_ENTRY_MORE = 5 + FEED_HIDE = 6 + FEED_SHOW = 7 + FEED_ENTRY_MESSAGE = 8 ENTRY_UPDATE = 1 ENTRY_DELETE = 2 @@ -39,14 +37,16 @@ const ( ) var ( - Subscribe = make(chan Subscriber, 10) - Unsubscribe = make(chan string, 10) - Publish = make(chan model.Event, 10) - P2P = make(chan *websocket.Conn, 10) + Subscribe = make(chan Subscriber, 10) + Unsubscribe = make(chan string, 10) + Publish = make(chan model.Event, 10) + ResourceEvent = make(chan model.SocketEvent, 10) WaitingList = list.New() Subscribers = list.New() + FeedSubscribers = make(map[string]interface{}) + GlobalSessions *session.Manager ) @@ -56,8 +56,8 @@ type Subscription struct { } type Subscriber struct { - Name string - Conn *websocket.Conn + Name string + Conn *websocket.Conn } func NewEvent(ep model.EventType, user, msg string) model.Event { @@ -65,6 +65,14 @@ func NewEvent(ep model.EventType, user, msg string) model.Event { return model.Event{ep, user, ts, strconv.Itoa(int(ts)), msg} } +func NewSocketEvent(msg []byte, ws *websocket.Conn, ch chan []byte) model.SocketEvent { + data := make(map[string]interface{}) + + json.Unmarshal(msg, &data) + + return model.SocketEvent{ws, ch, data["feedId"].(string), data["appId"].(string), data["orgId"].(string)} +} + func NewChannelEvent(ep model.EventType, user, msg string) model.Event { return NewEvent(ep, user, msg) } @@ -108,13 +116,17 @@ func FeedManager() { case sub := <-Subscribe: Subscribers.PushBack(sub) - Publish <- NewChannelEvent(CHANNEL_JOIN, sub.Name, "") - - case client := <-P2P: - data, _ := json.Marshal(NewSystemEvent(CHANNEL_MESSAGE, "system", "ok")) - client.WriteMessage(websocket.TextMessage, data) case event := <-Publish: + + // here must be handled where to send notification + // - or to all sockets + // - or to specific client/feed (single socket) + // - or to public feed (multiple sockets) + // + // could be setup by resource manager go routine + // room.FeedSubscribers[socketEvent.FeedId][channelID] = socketEvent + model.NewArchive(event) for ch := WaitingList.Back(); ch != nil; ch = ch.Prev() { @@ -159,9 +171,9 @@ func broadcastWebSocket(event model.Event) { } } -func init() { +func InitFeedRoom() { GlobalSessions, _ = session.NewManager("memory", `{"cookieName":"elasticfeedsessid","gclifetime":3600}`) - go FeedManager() go GlobalSessions.GC() + go FeedManager() } diff --git a/service/stream/model/event.go b/service/stream/model/event.go index eb8b776..40a287c 100644 --- a/service/stream/model/event.go +++ b/service/stream/model/event.go @@ -2,6 +2,8 @@ package model import ( "container/list" + + "github.com/gorilla/websocket" ) type EventType int @@ -14,6 +16,14 @@ type Event struct { Content string } +type SocketEvent struct { + Ws *websocket.Conn + Ch chan []byte + FeedId string + AppId string + OrgId string +} + const archiveSize = 1 var Archive = list.New() diff --git a/service/stream/router/default.go b/service/stream/router/default.go index acf4c10..a1dbd32 100644 --- a/service/stream/router/default.go +++ b/service/stream/router/default.go @@ -5,12 +5,13 @@ import ( "github.com/feedlabs/elasticfeed/service/stream/controller/channel" ) -func init() { - feedify.SetStaticPath("/static", "service/stream/static") - +func InitRouters() { feedify.Router("/stream/lp/join", &channel.LongPollingController{}, "get:Join") feedify.Router("/stream/lp/post", &channel.LongPollingController{}) feedify.Router("/stream/lp/fetch", &channel.LongPollingController{}, "get:Fetch") feedify.Router("/stream/ws/join", &channel.WebSocketController{}, "get:Join") + + feedify.Router("/stream/sse/join", &channel.SSEController{}, "get:Join") + feedify.Router("/stream/sse/post", &channel.SSEController{}) } diff --git a/service/stream/static/elasticfeed.js b/service/stream/static/elasticfeed.js deleted file mode 100644 index 4f155a8..0000000 --- a/service/stream/static/elasticfeed.js +++ /dev/null @@ -1,130 +0,0 @@ -function includeJs(jsFilePath) { - var js = document.createElement("script"); - - js.type = "text/javascript"; - js.src = jsFilePath; - - document.body.appendChild(js); -} - -includeJs('lib/feed.js'); -includeJs('lib/entry.js'); -includeJs('lib/channel.js'); -includeJs('lib/event.js'); - -(function(window) { - - /** @type {Object} */ - var defaultOptions = { - channel: { - url: 'localhost', - transport: 'ws' - }, - stylerFunction: function(data) { - } - } - - var elasticfeed = { - - /** @type {Object} */ - options: {}, - - /** @type {Object} */ - channelList: {}, - - /** @type {Object} */ - feedList: {}, - - init: function(options) { - this.options = _extend(defaultOptions, options); - }, - - initFeed: function(id, options) { - if (id == undefined) { - return false; - } - - if (this.feedList[id] == undefined) { - opts = _extend(this.options, options || {}); - channel = this.getChannel(opts.channel); - - this.feedList[id] = new Feed(id, opts, channel); - } - - return this.feedList[id]; - }, - - /** - * Returns Channel defined per API url - * @param options - * @param credential - * @returns {*} - */ - getChannel: function(options, credential) { - if (options.url == undefined) { - return false; - } - - if (this.channelList[options.url] == undefined) { - this.channelList[options.url] = new Channel(options, credential) - } - - return this.channelList[options.url]; - }, - - findFeed: function(id) { - if (this.feedList[id] == undefined) { - return false; - } - return this.feedList[id]; - }, - - findChannel: function(url) { - if (this.channelList[url] == undefined) { - return false; - } - return this.channelList[url]; - } - - }; - - // Helpers - - var _extend = function(a, b) { - var c = {}, prop; - for (prop in a) { - if (a.hasOwnProperty(prop)) { - c[prop] = a[prop]; - } - } - for (prop in b) { - if (b.hasOwnProperty(prop)) { - c[prop] = b[prop]; - } - } - return c; - } - - if ("function" === typeof define) { - define(function(require) { - return elasticfeed; - }); - } else { - window.elasticfeed = elasticfeed; - } - -}(window)); - -// Helpers - -Element.prototype.remove = function() { - this.parentElement.removeChild(this); -}; - -NodeList.prototype.remove = HTMLCollection.prototype.remove = function() { - for (var i = 0, len = this.length; i < len; i++) { - if (this[i] && this[i].parentElement) { - this[i].parentElement.removeChild(this[i]); - } - } -}; diff --git a/service/stream/static/lib/channel.js b/service/stream/static/lib/channel.js deleted file mode 100644 index 24d1ba6..0000000 --- a/service/stream/static/lib/channel.js +++ /dev/null @@ -1,280 +0,0 @@ -var Channel = (function() { - - const JOIN = 0 - const LEAVE = 1 - const MESSAGE = 2 - - var defaultOptions = { - id: null, - transport: 'ws', - connectOnInit: true - } - - function Channel(options) { - - /** @type {String} */ - this.id = _uniqueId(); - - /** @type {String} */ - this.url = null - - /** @type {Object} */ - this.options = _extend(defaultOptions, options); - - if (this.options.id != null) { - this.id = this.options.id; - } - - if (this.options.url != null) { - this.url = this.options.url; - } - - /** @type {Object} */ - this._handlers = {}; - - /** @type {WebSocket} */ - this._socket = null; - } - - // Handlers - - /** - * @param {Event} event - * @param {Function} callback - */ - Channel.prototype.on = function(name, callback) { - switch (name) { - case 'join': - type = JOIN - break; - case 'leave': - type = LEAVE - break; - case 'message': - type = MESSAGE - break; - default: - return false; - break; - } - if (this._handlers[type] == undefined) { - this._handlers[type] = [] - } - this._handlers[type].push(callback); - return true; - } - - // Events - - /** - * @param {Event} event - */ - Channel.prototype.onData = function(event) { - switch (event.type) { - case JOIN: - this.onJoin(event.user, event.ts) - break; - case LEAVE: - this.onLeave(event.user, event.ts) - break; - case MESSAGE: - this.onMessage(event.user, event.ts, event.content) - break; - } - } - - Channel.prototype.onJoin = function(chid, timestamp) { - for (var i in this._handlers[JOIN]) { - this._handlers[JOIN][i].call(this, chid, timestamp); - } - } - - Channel.prototype.onLeave = function(chid, timestamp) { - for (var i in this._handlers[LEAVE]) { - this._handlers[LEAVE][i].call(this, chid, timestamp); - } - } - - Channel.prototype.onMessage = function(chid, timestamp, data) { - systemEvent = new Event(data); - - for (var i in this._handlers[MESSAGE]) { - this._handlers[MESSAGE][i].call(this, chid, timestamp, systemEvent); - } - } - - // Connection - - Channel.prototype.isWebSocket = function() { - return this._socket != undefined; - } - - Channel.prototype.getConnection = function() { - } - - Channel.prototype.getWebSocketConnection = function() { - this._socket = new WebSocket('ws://localhost:10100/stream/ws/join?chid=' + this.id); - - self = this - this._socket.onmessage = function(event) { - event = new Event(JSON.parse(event.data)) - self.onData(event) - }; - - self = this - return { - send: function(data) { - self._socket.send(JSON.stringify(data)) - } - }; - } - - Channel.prototype.getLongPoolingConnection = function() { - var lastReceived = 0; - var isWait = false; - - this.getJSON('http://localhost:10100/stream/lp/join?chid=' + this.id, function(data) { - // should set timestamp to proper one! - }) - - self = this; - var fetch = function() { - if (isWait) { - return; - } - isWait = true; - self.getJSON("http://localhost:10100/stream/lp/fetch?lastReceived=" + lastReceived, function(data, code) { - - if (code == 4) { - isWait = false - } - - if (data == null) { - return; - } - - self.each(data, function(i, event) { - event = new Event(event) - self.onData(event) - - lastReceived = event.GetTimestamp(); - }); - isWait = false; - }); - } - - setInterval(fetch, 3000); - fetch() - - return { - send: function(data) { - self.post("/stream/lp/post", {chid: self.id, data: JSON.stringify(data)}, function(status) { - }); - } - }; - } - - Channel.prototype.load = function(url, callback) { - var xhr; - if (typeof XMLHttpRequest !== 'undefined') { - xhr = new XMLHttpRequest(); - } else { - var versions = ["MSXML2.XmlHttp.5.0", "MSXML2.XmlHttp.4.0", "MSXML2.XmlHttp.3.0", "MSXML2.XmlHttp.2.0", "Microsoft.XmlHttp"]; - for (var i = 0, len = versions.length; i < len; i++) { - try { - xhr = new ActiveXObject(versions[i]); - break; - } - catch (e) { - } - } - } - xhr.onreadystatechange = ensureReadiness; - function ensureReadiness() { - if (xhr.readyState < 4) { - return; - } - if (xhr.status !== 200) { - return; - } - if (xhr.readyState === 4) { - callback(xhr); - } - } - - xhr.open('GET', url, true); - xhr.send(''); - } - - // HTTP - - Channel.prototype.getJSON = function(url, callback) { - xhr = new XMLHttpRequest; - xhr.onreadystatechange = function() { - if (xhr.readyState == 4 && xhr.status == 200) { - if (xhr.responseText != "") { - data = JSON.parse(xhr.responseText); - callback.call(this, data, xhr.readyState) - } else { - callback.call(this, null, xhr.readyState) - } - } else { - callback.call(this, null, xhr.readyState) - } - } - xhr.open("GET", url) - xhr.send(); - } - - Channel.prototype.each = function(obj, callback) { - for (i = 0; i < obj.length; i++) { - value = callback.call(obj[i], i, obj[i]); - - if (value === false) { - break; - } - } - } - - Channel.prototype.queryString = function(obj) { - return Object.keys(obj).map(function(key) { - return encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]); - }).join('&'); - } - - Channel.prototype.post = function(url, data, callback) { - xhr1 = new XMLHttpRequest; - xhr1.onreadystatechange = function() { - if (xhr1.readyState == 4 && xhr1.status == 200) { - callback.call(this, xhr1.responseText) - } - } - dataString = this.queryString(data) - xhr1.open("POST", url + "?" + dataString, true); - xhr1.send(dataString); - } - - // Helpers - - var _extend = function(a, b) { - var c = {}, prop; - for (prop in a) { - if (a.hasOwnProperty(prop)) { - c[prop] = a[prop]; - } - } - for (prop in b) { - if (b.hasOwnProperty(prop)) { - c[prop] = b[prop]; - } - } - return c; - } - - var _uniqueId = function() { - return '_' + Math.random().toString(36).substr(2, 36); - } - - return Channel; - -})(); diff --git a/service/stream/static/lib/entry.js b/service/stream/static/lib/entry.js deleted file mode 100644 index ced554a..0000000 --- a/service/stream/static/lib/entry.js +++ /dev/null @@ -1,182 +0,0 @@ -var Entry = (function() { - - const UPDATE = 1 - const DELETE = 2 - const HIDE = 3 - const SHOW = 4 - - /** @type {Entry} */ - var localCache = {} - - function Entry(data, options) { - /** @type {String} */ - this.id = null; - - /** @type {String} */ - this.viewId = _uniqueId(); - - /** @type {String} */ - this.data = data; - - /** @type {Object} */ - this._feed = null; - - /** @type {Function} */ - this._styler = (options ? options.styler : undefined) || function() { - return data; - }; - - /** @type {Object} */ - this._handlers = {}; - } - - Entry.prototype.setParent = function(feed) { - this._feed = feed; - this.bindMessages(); - } - - Entry.prototype.getViewId = function() { - return this.viewId; - } - - // UI - - // TODO: - // should make animations, should be configurable by developer - // first level is style function; second level is render function - Entry.prototype.render = function() { - try { - document.getElementById(this.viewId).innerHTML = this._styler.call(this, JSON.stringify(this.data)); - } catch (e) { - // serious error - } - } - - // Events - - Entry.prototype.on = function(type, callback) { - switch (name) { - case 'delete': - type = DELETE - break; - case 'update': - type = UPDATE - break; - case 'hide': - type = HIDE - break; - case 'show': - type = SHOW - break; - default: - return false; - break; - } - if (this._handlers[type] == undefined) { - this._handlers[type] = [] - } - this._handlers[type].push(callback); - return true; - } - - Entry.prototype.onData = function(entryEvent) { - switch (entryEvent.type) { - case DELETE: - this.onDelete(entryEvent.ts) - break; - case UPDATE: - this.onUpdate(entryEvent.ts, entryEvent.content) - break; - case HIDE: - this.onHide(entryEvent.ts) - break; - case SHOW: - this.onShow(entryEvent.ts) - break; - } - } - - // Management - - Entry.prototype.update = function(timestamp, data) { - this.data = data; - this.render(); - } - - Entry.prototype.delete = function() { - this.unbindMessages(); - document.getElementById(this.viewId).remove(); - } - - Entry.prototype.hide = function() { - } - - Entry.prototype.show = function() { - } - - // API - - Entry.prototype.apiEntryUpdate = function(data) { - } - - Entry.prototype.apiMetricSave = function(data) { - } - - // Events callbacks - - Entry.prototype.onUpdate = function(timestamp, data) { - this.update(timestamp, data); - - for (var i in this._handlers[UPDATE]) { - this._handlers[UPDATE][i].call(this, timestamp, data); - } - } - - Entry.prototype.onDelete = function(timestamp) { - for (var i in this._handlers[DELETE]) { - this._handlers[DELETE][i].call(this, timestamp); - } - } - - Entry.prototype.onHide = function(timestamp) { - for (var i in this._handlers[HIDE]) { - this._handlers[HIDE][i].call(this, timestamp); - } - } - - Entry.prototype.onShow = function(timestamp) { - for (var i in this._handlers[SHOW]) { - this._handlers[SHOW][i].call(this, timestamp); - } - } - - // Handlers - - Entry.prototype.bindMessages = function() { - var self = this; - this.__bindFeed = this._feed.on('entry-message', function(ts, entryEvent) { - if (entryEvent.id == self.id || entryEvent.id == '*') { - self.onData(entryEvent); - } - }); - } - - Entry.prototype.unbindMessages = function() { - this._feed.off(this.__bindFeed); - } - - // Getters - - Entry.prototype.getTimestamp = function() { - return this.ts; - } - - // Helpers - - var _uniqueId = function() { - return '_' + Math.random().toString(36).substr(2, 36); - } - - return Entry; - -})(); diff --git a/service/stream/static/lib/event.js b/service/stream/static/lib/event.js deleted file mode 100644 index 5706608..0000000 --- a/service/stream/static/lib/event.js +++ /dev/null @@ -1,48 +0,0 @@ -var Event = (function() { - - function Event(event) { - - /** @type {String} */ - this.id = event.Id || null; - - /** @type {Integer} */ - this.ts = event.Timestamp; - - /** @type {Integer} */ - this.actionGroup = null - - /** @type {Integer} */ - this.actionType = null - - /** @type {String} */ - this.user = event.User - - /** @type {String} */ - this.type = event.Type - - /** @type {String} */ - this.contentType = 'string' - - /** @type {String} */ - try { - this.content = JSON.parse(event.Content) - this.contentType = 'json' - } catch (e) { - this.content = event.Content - } - } - - Event.prototype.GetTimestamp = function() { - return this.ts; - } - - Event.prototype.PrintContent = function() { - if (this.contentType == 'string') { - return this.content - } - return JSON.stringify(this.content) - } - - return Event; - -})(); diff --git a/service/stream/static/lib/feed.js b/service/stream/static/lib/feed.js deleted file mode 100644 index 626fb6f..0000000 --- a/service/stream/static/lib/feed.js +++ /dev/null @@ -1,346 +0,0 @@ -var Feed = (function() { - - const SYSTEM_FEED_MESSAGE = 1 - - const RELOAD = 1 - const EMPTY = 2 - const ENTRY_NEW = 3 - const ENTRY_INIT = 4 - const ENTRY_MORE = 5 - const HIDE = 6 - const SHOW = 7 - const ENTRY_MESSAGE = 8 - - const AUTHENTICATED = 100 - const AUTHENTICATION_REQUIRED = 101 - const AUTHENTICATION_FAILED = 102 - const LOGGED_OUT = 103 - - /** @type {Feed} */ - var localCache = {} - - /** @type {Object} */ - var globalOptions = { - feedId: '', - outputContainerId: 'defaultContainerId', - defaultElementLayout: '', - defaultElementCount: 0 - }; - - /** @type {Object} */ - var globalCredential = { - username: null, - token: null, - method: 'basic' - }; - - function Feed(id, options, channel) { - - /** @type {String} */ - this.id = id; - - /** @type {Channel} */ - this.channel = channel; - - /** @type {Array} */ - this.entryList = []; - - /** @type {Object} */ - if (this.channel.options.transport == 'ws') { - if (this.channel._socket == undefined) { - this.socket = this.channel.getWebSocketConnection(); - } else { - this.socket = this.channel._socket; - } - } else if (this.channel.options.transport == 'lp') { - this.socket = this.channel.getLongPoolingConnection(); - } - - /** @type {Object} */ - this.options = _extend(globalOptions, options); - - /** @type {Function} */ - this.stylerFunction = options.stylerFunction || this._stylerFunction; - - /** @type {Function} */ - this.renderFunction = options.renderFunction || this._renderFunction; - - /** @type {DOM} */ - this.outputContainer = document.getElementById(this.options.outputContainerId); - - this.bindChannel(this.channel); - - /** @type {Object} */ - this._handlers = {}; - } - - Feed.prototype.on = function(name, callback) { - switch (name) { - case 'reload': - type = RELOAD - break; - case 'empty': - type = EMPTY - break; - case 'entry': - type = ENTRY_NEW - break; - case 'entry-init': - type = ENTRY_INIT - break; - case 'entry-more': - type = ENTRY_MORE - break; - case 'hide': - type = HIDE - break; - case 'show': - type = SHOW - break; - case 'entry-message': - type = ENTRY_MESSAGE - break; - case 'authenticated': - type = AUTHENTICATED - break; - case 'authentication-required': - type = AUTHENTICATION_REQUIRED - break; - case 'authentication-failed': - type = AUTHENTICATION_FAILED - break; - case 'logout': - type = LOGGED_OUT - break; - default: - break; - } - if (this._handlers[type] == undefined) { - this._handlers[type] = [] - } - this._handlers[type].push(callback); - - return callback; - } - - Feed.prototype.off = function(callback) { - for (var i in this._handlers) { - for (var x in this._handlers[i]) { - if (this._handlers[i][x] == callback) { - delete this._handlers[i][x]; - return; - } - } - } - } - - Feed.prototype.onData = function(feedEvent) { - switch (feedEvent.type) { - case RELOAD: - this.onReload(feedEvent.ts) - break; - case EMPTY: - this.onEmpty(feedEvent.ts) - break; - case ENTRY_NEW: - this.onEntryNew(feedEvent.ts, feedEvent.content) - break; - case ENTRY_INIT: - this.onEntryInit(feedEvent.ts, feedEvent.content) - break; - case ENTRY_MORE: - this.onEntryMore(feedEvent.ts, feedEvent.content) - break; - case HIDE: - this.onHide(feedEvent.ts) - break; - case SHOW: - this.onShow(feedEvent.ts) - break; - case ENTRY_MESSAGE: - this.onEntryMessage(feedEvent.ts, feedEvent.content) - break; - case AUTHENTICATED: - this.onAuthenticated(feedEvent.ts, feedEvent.content) - break; - case AUTHENTICATION_REQUIRED: - this.onAuthenticationRequired(feedEvent.ts, feedEvent.content) - break; - case AUTHENTICATION_FAILED: - this.onAuthenticationFailed(feedEvent.ts, feedEvent.content) - break; - case LOGGED_OUT: - this.onLogout(feedEvent.ts, feedEvent.content) - break; - } - } - - // Events callbacks - - Feed.prototype.onReload = function(timestamp) { - for (var i in this._handlers[RELOAD]) { - this._handlers[RELOAD][i].call(this, timestamp); - } - } - - Feed.prototype.onEmpty = function(timestamp) { - for (var i in this._handlers[EMPTY]) { - this._handlers[EMPTY][i].call(this, timestamp); - } - } - - Feed.prototype.onEntryNew = function(timestamp, data) { - entry = new Entry(data, {styler: this.stylerFunction}); - - this.addEntry(entry) - - for (var i in this._handlers[ENTRY_NEW]) { - this._handlers[ENTRY_NEW][i].call(this, timestamp, entry); - } - } - - Feed.prototype.onEntryInit = function(timestamp, data) { - entries = JSON.parse(data); - - for (var i in this._handlers[ENTRY_INIT]) { - this._handlers[ENTRY_INIT][i].call(this, timestamp, entries); - } - } - - Feed.prototype.onEntryMore = function(timestamp, data) { - entries = JSON.parse(data); - - for (var i in this._handlers[ENTRY_MORE]) { - this._handlers[ENTRY_MORE][i].call(this, timestamp, entries); - } - } - - Feed.prototype.onHide = function(timestamp) { - for (var i in this._handlers[HIDE]) { - this._handlers[HIDE][i].call(this, timestamp); - } - } - - Feed.prototype.onShow = function(timestamp) { - for (var i in this._handlers[SHOW]) { - this._handlers[SHOW][i].call(this, timestamp); - } - } - - Feed.prototype.onEntryMessage = function(timestamp, content) { - entryEvent = new Event(content); - - for (var i in this._handlers[ENTRY_MESSAGE]) { - this._handlers[ENTRY_MESSAGE][i].call(this, timestamp, entryEvent); - } - } - - Feed.prototype.onAuthenticated = function(timestamp, content) { - for (var i in this._handlers[AUTHENTICATED]) { - this._handlers[AUTHENTICATED][i].call(this, timestamp); - } - } - - Feed.prototype.onAuthenticationRequired = function(timestamp, content) { - for (var i in this._handlers[AUTHENTICATION_REQUIRED]) { - this._handlers[AUTHENTICATION_REQUIRED][i].call(this, timestamp); - } - } - - Feed.prototype.onAuthenticationFailed = function(timestamp, content) { - for (var i in this._handlers[AUTHENTICATION_FAILED]) { - this._handlers[AUTHENTICATION_FAILED][i].call(this, timestamp); - } - } - - Feed.prototype.onLogout = function(timestamp, content) { - for (var i in this._handlers[LOGGED_OUT]) { - this._handlers[LOGGED_OUT][i].call(this, timestamp); - } - } - - // Entries management - - Feed.prototype.addEntry = function(entry) { - - // types - // add by: timestamp up/down; always to top; always to bottom - - entry.setParent(this); - this.entryList.push(entry); - - this.outputContainer.innerHTML = '
' + this.outputContainer.innerHTML; - - entry.render(); - } - - Feed.prototype.deleteEntry = function(entry) { - entry.delete(); - } - - Feed.prototype.updateEntry = function(entry, data) { - } - - Feed.prototype.empty = function() { - for (var i in this.entryList) { - this.deleteEntry(this.entryList[i]); - delete this.entryList[i]; - } - this.entryList = [] - } - - Feed.prototype.findEntry = function(id) { - } - - // UI - - Feed.prototype.render = function(id) { - for (var i in this.entryList) { - this.entryList[i].render(); - } - } - - // Handlers - - Feed.prototype.bindChannel = function(channel) { - var self = this; - channel.on('message', function(chid, ts, systemEvent) { - if (systemEvent.type == SYSTEM_FEED_MESSAGE) { - feedEvent = new Event(systemEvent.content); - if (feedEvent.user == self.id || feedEvent.user == '*') { - self.onData(feedEvent); - } - } - }); - } - - // Stylers - - Feed.prototype._stylerFunction = function(data) { - return JSON.stringify(data); - } - - Feed.prototype._renderFunction = function(data) { - return JSON.stringify(data); - } - - // Helpers - - var _extend = function(a, b) { - var c = {}, prop; - for (prop in a) { - if (a.hasOwnProperty(prop)) { - c[prop] = a[prop]; - } - } - for (prop in b) { - if (b.hasOwnProperty(prop)) { - c[prop] = b[prop]; - } - } - return c; - } - - return Feed; - -})(); diff --git a/service/stream/static/lp.html b/service/stream/static/lp.html deleted file mode 100644 index 85eca45..0000000 --- a/service/stream/static/lp.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - - -
- - diff --git a/service/stream/static/ws.html b/service/stream/static/ws.html deleted file mode 100644 index 032a291..0000000 --- a/service/stream/static/ws.html +++ /dev/null @@ -1,65 +0,0 @@ - - - - - -
-
-
-
- - diff --git a/service/stream/stream.go b/service/stream/stream.go index 948887d..56469c0 100644 --- a/service/stream/stream.go +++ b/service/stream/stream.go @@ -1,8 +1,17 @@ package stream import ( - _ "github.com/feedlabs/elasticfeed/service/stream/router" - _ "github.com/feedlabs/elasticfeed/service/stream/controller" + "github.com/feedlabs/elasticfeed/service/stream/router" + "github.com/feedlabs/elasticfeed/service/stream/controller" ) -func init() {} +type StreamService struct {} + +func (this *StreamService) Init() { + router.InitRouters() + controller.InitRooms() +} + +func NewMetricService() *StreamService { + return &StreamService{} +} diff --git a/service/system/router/status.go b/service/system/router/status.go index 79a01a3..c6ba3cf 100644 --- a/service/system/router/status.go +++ b/service/system/router/status.go @@ -5,6 +5,6 @@ import ( "github.com/feedlabs/elasticfeed/service/system/controller" ) -func init() { +func InitRouters() { feedify.Router("/system/status", &controller.StatusController{}, "get:Get") } diff --git a/service/system/system.go b/service/system/system.go index f4352a0..bb8dca2 100644 --- a/service/system/system.go +++ b/service/system/system.go @@ -1,8 +1,15 @@ package system import ( - _ "github.com/feedlabs/elasticfeed/service/system/controller" - _ "github.com/feedlabs/elasticfeed/service/system/router" + "github.com/feedlabs/elasticfeed/service/system/router" ) -func init() {} +type SystemService struct {} + +func (this *SystemService) Init() { + router.InitRouters() +} + +func NewMetricService() *SystemService { + return &SystemService{} +} diff --git a/website/.buildpacks b/website/.buildpacks new file mode 100644 index 0000000..f85b304 --- /dev/null +++ b/website/.buildpacks @@ -0,0 +1,2 @@ +https://github.com/heroku/heroku-buildpack-ruby.git +https://github.com/hashicorp/heroku-buildpack-middleman.git diff --git a/website/Gemfile b/website/Gemfile new file mode 100644 index 0000000..8fc173c --- /dev/null +++ b/website/Gemfile @@ -0,0 +1,5 @@ +source 'https://rubygems.org' + +ruby "2.0.0" + +gem 'middleman-hashicorp', github: 'hashicorp/middleman-hashicorp' diff --git a/website/Gemfile.lock b/website/Gemfile.lock new file mode 100644 index 0000000..170034e --- /dev/null +++ b/website/Gemfile.lock @@ -0,0 +1,173 @@ +GIT + remote: git://github.com/hashicorp/middleman-hashicorp.git + revision: b82c2c2fdc244cd0bd529ff27cfab24e43f07708 + specs: + middleman-hashicorp (0.1.0) + bootstrap-sass (~> 3.2) + builder (~> 3.2) + less (~> 2.6) + middleman (~> 3.3) + middleman-livereload (~> 3.3) + middleman-minify-html (~> 3.4) + middleman-syntax (~> 2.0) + rack-contrib (~> 1.1) + rack-rewrite (~> 1.5) + rack-ssl-enforcer (~> 0.2) + redcarpet (~> 3.1) + therubyracer (~> 0.12) + thin (~> 1.6) + +GEM + remote: https://rubygems.org/ + specs: + activesupport (4.1.6) + i18n (~> 0.6, >= 0.6.9) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) + thread_safe (~> 0.1) + tzinfo (~> 1.1) + bootstrap-sass (3.2.0.2) + sass (~> 3.2) + builder (3.2.2) + celluloid (0.16.0) + timers (~> 4.0.0) + chunky_png (1.3.3) + coffee-script (2.3.0) + coffee-script-source + execjs + coffee-script-source (1.8.0) + commonjs (0.2.7) + compass (1.0.1) + chunky_png (~> 1.2) + compass-core (~> 1.0.1) + compass-import-once (~> 1.0.5) + rb-fsevent (>= 0.9.3) + rb-inotify (>= 0.9) + sass (>= 3.3.13, < 3.5) + compass-core (1.0.1) + multi_json (~> 1.0) + sass (>= 3.3.0, < 3.5) + compass-import-once (1.0.5) + sass (>= 3.2, < 3.5) + daemons (1.1.9) + em-websocket (0.5.1) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0.6.0) + erubis (2.7.0) + eventmachine (1.0.3) + execjs (2.2.2) + ffi (1.9.6) + haml (4.0.5) + tilt + hike (1.2.3) + hitimes (1.2.2) + hooks (0.4.0) + uber (~> 0.0.4) + htmlcompressor (0.1.2) + http_parser.rb (0.6.0) + i18n (0.6.11) + json (1.8.1) + kramdown (1.5.0) + less (2.6.0) + commonjs (~> 0.2.7) + libv8 (3.16.14.7) + listen (2.7.11) + celluloid (>= 0.15.2) + rb-fsevent (>= 0.9.3) + rb-inotify (>= 0.9) + middleman (3.3.6) + coffee-script (~> 2.2) + compass (>= 1.0.0, < 2.0.0) + compass-import-once (= 1.0.5) + execjs (~> 2.0) + haml (>= 4.0.5) + kramdown (~> 1.2) + middleman-core (= 3.3.6) + middleman-sprockets (>= 3.1.2) + sass (>= 3.4.0, < 4.0) + uglifier (~> 2.5) + middleman-core (3.3.6) + activesupport (~> 4.1.0) + bundler (~> 1.1) + erubis + hooks (~> 0.3) + i18n (~> 0.6.9) + listen (>= 2.7.9, < 3.0) + padrino-helpers (~> 0.12.3) + rack (>= 1.4.5, < 2.0) + rack-test (~> 0.6.2) + thor (>= 0.15.2, < 2.0) + tilt (~> 1.4.1, < 2.0) + middleman-livereload (3.3.4) + em-websocket (~> 0.5.1) + middleman-core (~> 3.2) + rack-livereload (~> 0.3.15) + middleman-minify-html (3.4.0) + htmlcompressor (~> 0.1.0) + middleman-core (>= 3.2) + middleman-sprockets (3.3.10) + middleman-core (~> 3.3) + sprockets (~> 2.12.1) + sprockets-helpers (~> 1.1.0) + sprockets-sass (~> 1.2.0) + middleman-syntax (2.0.0) + middleman-core (~> 3.2) + rouge (~> 1.0) + minitest (5.4.2) + multi_json (1.10.1) + padrino-helpers (0.12.4) + i18n (~> 0.6, >= 0.6.7) + padrino-support (= 0.12.4) + tilt (~> 1.4.1) + padrino-support (0.12.4) + activesupport (>= 3.1) + rack (1.5.2) + rack-contrib (1.1.0) + rack (>= 0.9.1) + rack-livereload (0.3.15) + rack + rack-rewrite (1.5.0) + rack-ssl-enforcer (0.2.8) + rack-test (0.6.2) + rack (>= 1.0) + rb-fsevent (0.9.4) + rb-inotify (0.9.5) + ffi (>= 0.5.0) + redcarpet (3.2.0) + ref (1.0.5) + rouge (1.7.2) + sass (3.4.6) + sprockets (2.12.2) + hike (~> 1.2) + multi_json (~> 1.0) + rack (~> 1.0) + tilt (~> 1.1, != 1.3.0) + sprockets-helpers (1.1.0) + sprockets (~> 2.0) + sprockets-sass (1.2.0) + sprockets (~> 2.0) + tilt (~> 1.1) + therubyracer (0.12.1) + libv8 (~> 3.16.14.0) + ref + thin (1.6.3) + daemons (~> 1.0, >= 1.0.9) + eventmachine (~> 1.0) + rack (~> 1.0) + thor (0.19.1) + thread_safe (0.3.4) + tilt (1.4.1) + timers (4.0.1) + hitimes + tzinfo (1.2.2) + thread_safe (~> 0.1) + uber (0.0.10) + uglifier (2.5.3) + execjs (>= 0.3.0) + json (>= 1.8.0) + +PLATFORMS + ruby + +DEPENDENCIES + middleman-hashicorp! diff --git a/website/Procfile b/website/Procfile new file mode 100644 index 0000000..58361e4 --- /dev/null +++ b/website/Procfile @@ -0,0 +1 @@ +web: bundle exec thin start -p $PORT diff --git a/website/README.md b/website/README.md new file mode 100644 index 0000000..dd986e4 --- /dev/null +++ b/website/README.md @@ -0,0 +1,24 @@ +# Elasticfeed Website + +This subdirectory contains the entire source for the [Elasticfeed website](http://www.elasticfeed.io). +This is a [Middleman](http://middlemanapp.com) project, which builds a static +site from these source files. + +## Contributions Welcome! + +If you find a typo or you feel like you can improve the HTML, CSS, or +JavaScript, we welcome contributions. Feel free to open issues or pull +requests like any normal GitHub project, and we'll merge it in. + +## Running the Site Locally + +Running the site locally is simple. Clone this repo and run the following +commands: + +``` +$ bundle +$ ELASTICFEED_DISABLE_DOWNLOAD_FETCH=true ELASTICFEED_VERSION=1.0 bundle exec middleman server +``` + +Then open up `localhost:4567`. Note that some URLs you may need to append +".html" to make them work (in the navigation and such). diff --git a/website/Vagrantfile b/website/Vagrantfile new file mode 100644 index 0000000..b8da4b2 --- /dev/null +++ b/website/Vagrantfile @@ -0,0 +1,25 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +$script = < + + + + + + <%= yield %> + +
+ + + + + + diff --git a/website/source/robots.txt b/website/source/robots.txt new file mode 100644 index 0000000..190c6ce --- /dev/null +++ b/website/source/robots.txt @@ -0,0 +1,8 @@ +--- +layout: false +noindex: true +--- + +User-agent: * +Disallow: /404 +Disallow: /500 diff --git a/workflow/hook.go b/workflow/hook.go new file mode 100644 index 0000000..f2afc6f --- /dev/null +++ b/workflow/hook.go @@ -0,0 +1,3 @@ +package workflow + +type Hook struct {} diff --git a/workflow/manager.go b/workflow/manager.go new file mode 100644 index 0000000..066d46d --- /dev/null +++ b/workflow/manager.go @@ -0,0 +1,39 @@ +package workflow + +import ( + "github.com/feedlabs/elasticfeed/plugin" + "github.com/feedlabs/elasticfeed/event" +) + +type WorkflowManager struct { + pManager *plugin.PluginManager + eManager *event.EventManager + + workflows []*Workflow + template interface{} +} + +func (this *WorkflowManager) InitTemplate(t interface{}) { + // verify event availability into EventsManger + // verify hooks workflows + this.template = t +} + +func (this *WorkflowManager) CreateFeedWorkflow(f interface {}, data map[string]interface {}) *Workflow { + w := NewWorkflow(data, f, this) + w.Init() + this.workflows = append(this.workflows, w) + return w +} + +func NewWorkflowManager(tpl interface{}, pm *plugin.PluginManager, em *event.EventManager) *WorkflowManager { + // load template if not passed + if tpl == nil { + tpl = make(map[string]interface {}) + } + + wm := &WorkflowManager{pm, em, nil, nil} + wm.InitTemplate(tpl) + + return wm +} diff --git a/workflow/template/default.json b/workflow/template/default.json new file mode 100644 index 0000000..4087d72 --- /dev/null +++ b/workflow/template/default.json @@ -0,0 +1,31 @@ +{ + "profiler": { + "default-profiler": true, + "mem": 256, + "cpus": 4, + "bandwidth": 1000 + }, + "storing": { + "create-entry": { + "indexer": [], + "crawler": [] + } + }, + "processing": { + "feed-maintainer": { + "scenario": [] + } + }, + "distributing": { + "push-entry-to-ui": { + "sensor": [], + "pipeline": [] + } + }, + "learning": { + "create-metric": { + "sensor": [], + "scenario": [] + } + } +} diff --git a/workflow/workflow.go b/workflow/workflow.go new file mode 100644 index 0000000..727c95c --- /dev/null +++ b/workflow/workflow.go @@ -0,0 +1,49 @@ +package workflow + +import ( + "github.com/feedlabs/elasticfeed/plugin" +) + +type Workflow struct { + feed interface{} + manager *WorkflowManager + + profiler *plugin.Profiler + data map[string]interface{} +} + +func (this *Workflow) GetManager() *WorkflowManager { + return nil +} + +func (this *Workflow) GetFeed() *interface{} { + return nil +} + +func (this *Workflow) GetProfiler() *plugin.Profiler { + return this.profiler +} + +func (this *Workflow) Init() { + // verify Feed.Workflowfile stricture; does match WorkflowManager Templating + // verify plugins availability: this.manager.findPlugin() + // run Plugins if require specific Profiler + // bind Feed to system Events: this.manager.BindToSystemEvents() +} + +func (this *Workflow) DispatchIndexerHook(data interface{}) interface{} { + return data +} + +func (this *Workflow) DispatchPipelineHook(data interface{}) interface{} { + return data +} + +func NewWorkflow(data map[string]interface{}, f interface{}, wm *WorkflowManager) *Workflow { + p := plugin.NewProfiler(data["profiler"].(map[string]string)) + w := &Workflow{f, wm, p, data} + + w.Init() + + return w +}