commit b2502bd29e175e02949ffd74338d82cad601994a Author: didier amyot Date: Sat Dec 13 18:03:08 2014 -0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..daf913b --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..44bcb32 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014 didier amyot + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/data/data.go b/data/data.go new file mode 100644 index 0000000..1a224c9 --- /dev/null +++ b/data/data.go @@ -0,0 +1,94 @@ +package data + +import ( + "fmt" + "log" + "net" + "net/http" + + "github.com/gorilla/mux" + "github.com/gorilla/securecookie" + "github.com/influxdb/influxdb/client" + "gopkg.in/mgo.v2/bson" +) + +var hashKey = []byte("0ea20ffa64fb127136be5bb9fb0814289267f6d162c81f06af237a09e375d182bada41b9ba14eb4d34f73c9c8b792fc0") +var s = securecookie.New(hashKey, nil) + +type Data struct { + IncluxDB *client.Client +} + +func (d *Data) CreateUserId(w http.ResponseWriter) bson.ObjectId { + id := bson.NewObjectId() + + if encoded, err := s.Encode("onepixel", map[string]string{"id": id.Hex()}); err == nil { + cookie := &http.Cookie{ + Name: "onepixel", + Value: encoded, + Path: "/", + } + http.SetCookie(w, cookie) + } else { + log.Println("cannot encrypt uuid") + } + return id +} + +func (d *Data) GetUserIdInCookie(r *http.Request) bson.ObjectId { + if cookie, err := r.Cookie("onepixel"); err == nil { + value := make(map[string]string) + if err = s.Decode("onepixel", cookie.Value, &value); err == nil { + if bson.IsObjectIdHex(value["id"]) { + return bson.ObjectIdHex(value["id"]) + } + } + } + return "" +} + +func (d *Data) GetAndSetId(w http.ResponseWriter, request *http.Request) bson.ObjectId { + if id := d.GetUserIdInCookie(request); id.Valid() { + return id + } else { + return d.CreateUserId(w) + } +} + +func (d *Data) ServeHTTP(w http.ResponseWriter, request *http.Request) { + vars := mux.Vars(request) + + ip, _, _ := net.SplitHostPort(request.RemoteAddr) + + userAgent := request.UserAgent() + useId := d.GetAndSetId(w, request) + url := vars["url"] + serieName := vars["serie_name"] + + d.AddDataPoint(serieName, url, userAgent, ip, useId) +} + +func (d *Data) CreateSeries(serieName, url, userAgent, ip string, userId bson.ObjectId) []*client.Series { + series := &client.Series{ + Name: serieName, + Columns: []string{"url", "user_id", "user_agent", "ip"}, + Points: [][]interface{}{ + {url, userId, userAgent, ip}, + }, + } + return []*client.Series{series} +} + +func (d *Data) AddDataPoint(serieName, url, userAgent, ip string, userId bson.ObjectId) { + fmt.Println("serieName :", serieName) + fmt.Println("url :", url) + fmt.Println("id :", userId.Hex()) + fmt.Println("userAgent :", userAgent) + fmt.Println("") + + series := d.CreateSeries(serieName, url, userAgent, ip, userId) + if err := d.IncluxDB.WriteSeries(series); err != nil { + log.Println(err) + } + +} diff --git a/data/data_test.go b/data/data_test.go new file mode 100644 index 0000000..964b56b --- /dev/null +++ b/data/data_test.go @@ -0,0 +1,42 @@ +package data + +import ( + "errors" + "net/http" + "testing" +) + +type MockResponseWriter struct { + header http.Header +} + +func NewMockResponseWriter() http.ResponseWriter { + var header http.Header = make(map[string][]string) + return &MockResponseWriter{header} +} + +func (m *MockResponseWriter) Header() http.Header { + return m.header +} + +func (m *MockResponseWriter) Write(b []byte) (int, error) { + return 500, errors.New("not implemented") +} + +func (m *MockResponseWriter) WriteHeader(c int) {} + +func TestSecureCookie(t *testing.T) { + w := NewMockResponseWriter() + d := new(Data) + + uuid := d.CreateUserId(w) + + if !uuid.Valid() { + t.Error("invalid uuid") + } + + if w.Header().Get("Set-Cookie") == "" { + t.Error("nothing in set in the cookie") + } + +} diff --git a/data/doc.go b/data/doc.go new file mode 100644 index 0000000..3b499af --- /dev/null +++ b/data/doc.go @@ -0,0 +1 @@ +package data \ No newline at end of file diff --git a/onepixel.go b/onepixel.go new file mode 100644 index 0000000..3b5f7e4 --- /dev/null +++ b/onepixel.go @@ -0,0 +1,43 @@ +package main + +import ( + "log" + "net/http" + "path" + "strconv" + + "github.com/didiercrunch/onepixel/data" + "github.com/gorilla/mux" +) + +const STATIC_DIR_PATH = "static/" + +func serveStatic(router *mux.Router) { + handler := func(w http.ResponseWriter, request *http.Request) { + vars := mux.Vars(request) + filepath := "/" + vars["path"] + w.Header().Set("Cache-Control", "public, max-age=43200") + http.ServeFile(w, request, path.Join(STATIC_DIR_PATH, filepath)) + } + router.HandleFunc("/{path:.*}", handler) +} + +func serveApi(router *mux.Router, apiHandler http.Handler) { + router.Handle("/api/{serie_name}/{url:.*}", apiHandler) +} + +func createMuxRouter() http.Handler { + r := mux.NewRouter() + serveApi(r, &data.Data{params.InfluxDBClient}) + serveStatic(r.PathPrefix("/").Subrouter()) + return r +} + +func main() { + r := createMuxRouter() + http.Handle("/", r) + address := params.Host + ":" + strconv.Itoa(params.Port) + if err := http.ListenAndServe(address, createMuxRouter()); err != nil { + log.Fatal(err) + } +} diff --git a/onepixel_test.go b/onepixel_test.go new file mode 100644 index 0000000..c9ecbf5 --- /dev/null +++ b/onepixel_test.go @@ -0,0 +1,2 @@ +package main + diff --git a/params.go b/params.go new file mode 100644 index 0000000..150f6c0 --- /dev/null +++ b/params.go @@ -0,0 +1,81 @@ +package main + +import ( + "io/ioutil" + "github.com/influxdb/influxdb/client" + + goyaml "gopkg.in/yaml.v2" +) + +const PARAM_FILE string = "params.yml" + +type Params struct { + Port int `yaml:"port,omitempty"` + Host string `yaml:"host,omitempty"` + InfluxDBClientConfig *client.ClientConfig `yaml:"influxdb,omitempty"` + InfluxDBClient *client.Client `yaml:"-"` +} + +func (this *Params) LoadFromYamlFile(fileName string) error { + data, err := ioutil.ReadFile(fileName) + if err != nil { + return err + } + return this.LoadFromYamlData(data) +} + +func (this *Params) LoadFromYamlData(data []byte) error { + var err error + p := new(Params) + if err = goyaml.Unmarshal(data, p); err != nil { + return err + } + + if p.Host != "" { + this.Host = p.Host + } + if p.Port > 0 { + this.Port = p.Port + } + if p.InfluxDBClientConfig == nil { + return nil + } + if p.InfluxDBClientConfig.Host != "" { + this.InfluxDBClientConfig.Host = p.InfluxDBClientConfig.Host + } + if p.InfluxDBClientConfig.Database != "" { + this.InfluxDBClientConfig.Database = p.InfluxDBClientConfig.Database + } + if p.InfluxDBClientConfig.Password != "" { + this.InfluxDBClientConfig.Password = p.InfluxDBClientConfig.Password + } + if p.InfluxDBClientConfig.Username != "" { + this.InfluxDBClientConfig.Username = p.InfluxDBClientConfig.Username + } + this.InfluxDBClientConfig.IsSecure = p.InfluxDBClientConfig.IsSecure + + return nil +} + +func (this *Params) initInfluxDB() error { + var err error + this.InfluxDBClient, err = client.NewClient(this.InfluxDBClientConfig) + + return err +} + +func GetDefaultParams() *Params { + influxdbClientConfig := &client.ClientConfig{} + return &Params{7777, "localhost", influxdbClientConfig, nil} +} + +var params = GetDefaultParams() + +func init() { + if err := params.LoadFromYamlFile(PARAM_FILE); err != nil { + panic(err) + } + if err := params.initInfluxDB(); err != nil { + panic(err) + } +} diff --git a/params.yml b/params.yml new file mode 100644 index 0000000..5f10603 --- /dev/null +++ b/params.yml @@ -0,0 +1,24 @@ +--- + + +# This is onepixel params' file. The file is in yaml format. The file +# should be auto-documented + + +# the port where the onepixel web site will run +port: 6900 + +# the host name. 0.0.0.0 to share the service with everyone on the network. +# localhost for running on localhost without the possibility to share the +# service +host: 0.0.0.0 #localhost + + + +# all the params relative to inflixdb +influxdb: + host: ~ + username: root + password: root + database: onepixel + issecure: yes diff --git a/params_test.go b/params_test.go new file mode 100644 index 0000000..65627c3 --- /dev/null +++ b/params_test.go @@ -0,0 +1,45 @@ +package main + +import ( + "testing" +) + +const yaml_params = ` +--- + +port: 9999 + +host: 0.0.0.0 #localhost + +# all the params relative to mongodb +influxdb: + host: "bigtits.com" + username: "alice" + password: "qwerty" + database: "tits" + issecure: yes +` + +func TestLoadFromYamlData(t *testing.T) { + p := GetDefaultParams() + if err := p.LoadFromYamlData([]byte(yaml_params)); err != nil { + t.Error(err) + return + } + if p.Port != 9999 { + t.Error("port") + } + if p.Host != "0.0.0.0" { + t.Error("host") + } + if p.InfluxDBClientConfig.Host != "bigtits.com" { + t.Error("InfluxDBClientConfig.Host", p.InfluxDBClientConfig.Host, " fuck") + } + if p.InfluxDBClientConfig.Database != "tits" { + t.Error("InfluxDBClientConfig.Database") + } + if !p.InfluxDBClientConfig.IsSecure { + t.Error("InfluxDBClientConfig.IsSecure") + } + +} diff --git a/static/app/coffee/app.coffee b/static/app/coffee/app.coffee new file mode 100644 index 0000000..5938c34 --- /dev/null +++ b/static/app/coffee/app.coffee @@ -0,0 +1,17 @@ +define [ + "angular" + "filters" + "services" + "directives" + "controllers" + "angularRoute" +], (angular, filters, services, directives, controllers) -> + + # Declare app level module which depends on filters, and services + angular.module "myApp", [ + "ngRoute" + "myApp.filters" + "myApp.services" + "myApp.directives" + "myApp.controllers" + ] diff --git a/static/app/coffee/controllers/myctrl2.coffee b/static/app/coffee/controllers/myctrl2.coffee new file mode 100644 index 0000000..d1bcb80 --- /dev/null +++ b/static/app/coffee/controllers/myctrl2.coffee @@ -0,0 +1,14 @@ +define [], -> + [ + "$scope" + "$http" + ($scope, $http) -> + + # You can access the scope of the controller from here + $scope.welcomeMessage = "hey this is myctrl2.js!" + + # because this has happened asynchroneusly we've missed + # Angular's initial call to $apply after the controller has been loaded + # hence we need to explicityly call it at the end of our Controller constructor + $scope.$apply() + ] diff --git a/static/app/coffee/directives.coffee b/static/app/coffee/directives.coffee new file mode 100644 index 0000000..de6c134 --- /dev/null +++ b/static/app/coffee/directives.coffee @@ -0,0 +1,14 @@ +define [ + "angular" + "services" +], (angular, services) -> + + # Directives + angular.module("myApp.directives", ["myApp.services"]).directive "appVersion", [ + "version" + (version) -> + return (scope, elm, attrs) -> + elm.text version + return + ] + return diff --git a/static/app/coffee/main.coffee b/static/app/coffee/main.coffee new file mode 100644 index 0000000..5a53695 --- /dev/null +++ b/static/app/coffee/main.coffee @@ -0,0 +1,32 @@ +require.config + paths: + angular: "../bower_components/angular/angular" + angularRoute: "../bower_components/angular-route/angular-route" + angularMocks: "../bower_components/angular-mocks/angular-mocks" + text: "../bower_components/requirejs-text/text" + + shim: + angular: + exports: "angular" + + angularRoute: ["angular"] + angularMocks: + deps: ["angular"] + exports: "angular.mock" + + priority: ["angular"] + + +#http://code.angularjs.org/1.2.1/docs/guide/bootstrap#overview_deferred-bootstrap +window.name = "NG_DEFER_BOOTSTRAP!" +require [ + "angular" + "app" + "routes" +], (angular, app, routes) -> + $html = angular.element(document.getElementsByTagName("html")[0]) + angular.element().ready -> + angular.resumeBootstrap [app["name"]] + return + + return diff --git a/static/app/coffee/routes.coffee b/static/app/coffee/routes.coffee new file mode 100644 index 0000000..5cb251d --- /dev/null +++ b/static/app/coffee/routes.coffee @@ -0,0 +1,17 @@ +define [ + "angular" + "app" +], (angular, app) -> + app.config [ + "$routeProvider" + ($routeProvider) -> + $routeProvider.when "/view1", + templateUrl: "app/partials/partial1.html" + controller: "MyCtrl1" + + $routeProvider.when "/view2", + templateUrl: "app/partials/partial2.html" + controller: "MyCtrl2" + + $routeProvider.otherwise redirectTo: "/view1" + ] diff --git a/static/app/coffee/services.coffee b/static/app/coffee/services.coffee new file mode 100644 index 0000000..31d1798 --- /dev/null +++ b/static/app/coffee/services.coffee @@ -0,0 +1,8 @@ +define ["angular"], (angular) -> + + # Services + + # Demonstrate how to register services + # In this case it is a simple value service. + angular.module("myApp.services", []).value "version", "0.1" + return diff --git a/static/gulpfile.js b/static/gulpfile.js new file mode 100644 index 0000000..c8dc66f --- /dev/null +++ b/static/gulpfile.js @@ -0,0 +1,34 @@ +var bower = require('gulp-bower'); +var gulp = require('gulp'); +var coffee = require('gulp-coffee'); +var help = require('gulp-task-listing'); +var clean = require('gulp-clean'); + + + +gulp.task('help', help); + +gulp.task('bower', function() { + return bower() + .pipe(gulp.dest('bower_components/')) +}); + +gulp.task('coffee', function() { + gulp.src('app/coffee/**/*.coffee') + .pipe(coffee({bare: true})) + .pipe(gulp.dest('./app/js/')) +}); + +gulp.task('clean', function(){ + return gulp.src(["js/", "bower_components/"], {read: false}) + .pipe(clean()) +}); + + +gulp.task('default', ['bower', 'coffee']); + + + +gulp.task('dev', ['bower', 'coffee'], function(){ + gulp.watch('coffee/**/*.coffee', ['coffee']); +}); diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..73ad580 --- /dev/null +++ b/static/index.html @@ -0,0 +1,33 @@ + + + + + + + + + Angular-RequireJS sample app + + + + + + + + +

AngularJS + RequireJS

+ + + + +
+ +
AngularJS + RequireJS seed app: v
+ + + diff --git a/static/package.json b/static/package.json new file mode 100644 index 0000000..ef210b5 --- /dev/null +++ b/static/package.json @@ -0,0 +1,9 @@ +{ + "dependencies": { + "gulp-task-listing": "~0.3.0", + "gulp-clean": "~0.3.1", + "gulp-coffee": "~2.2.0", + "gulp": "~3.8.9", + "gulp-bower": "0.0.7" + } +}