From dd0e8f6bf47e2c94e1c483ab1c620f2c8130a121 Mon Sep 17 00:00:00 2001 From: Darko Luketic Date: Thu, 24 Sep 2015 18:42:56 +0200 Subject: [PATCH] initial --- handler/common.go | 5 ++ handler/posten.go | 120 ++++++++++++++++++++++++++++++++++++ main.go | 28 +++++++++ model/rechnung.go | 14 +++++ nginx/voce.dev.luketic.conf | 22 +++++++ static/bower.json | 22 +++++++ static/index.html | 42 +++++++++++++ static/js/main.js | 106 +++++++++++++++++++++++++++++++ static/views/expense.html | 36 +++++++++++ static/views/home.html | 55 +++++++++++++++++ static/views/revenue.html | 36 +++++++++++ 11 files changed, 486 insertions(+) create mode 100644 handler/common.go create mode 100644 handler/posten.go create mode 100644 main.go create mode 100644 model/rechnung.go create mode 100644 nginx/voce.dev.luketic.conf create mode 100644 static/bower.json create mode 100644 static/index.html create mode 100644 static/js/main.js create mode 100644 static/views/expense.html create mode 100644 static/views/home.html create mode 100644 static/views/revenue.html diff --git a/handler/common.go b/handler/common.go new file mode 100644 index 0000000..ea614b7 --- /dev/null +++ b/handler/common.go @@ -0,0 +1,5 @@ +package handler + +import "regexp" + +var bsonRE = regexp.MustCompile(`[a-fA-F0-9]{24}`) diff --git a/handler/posten.go b/handler/posten.go new file mode 100644 index 0000000..95a179b --- /dev/null +++ b/handler/posten.go @@ -0,0 +1,120 @@ +package handler + +import ( + "encoding/json" + "net/http" + "time" + + "github.com/dalu/voce/model" + + "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +type PostHandler struct { + MS *mgo.Session + DB string + C string +} + +func (h *PostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "GET": + h.GET(w, r) + case "POST": + h.POST(w, r) + case "PUT": + h.PUT(w, r) + case "DELETE": + h.DELETE(w, r) + default: + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + } +} + +func (h *PostHandler) GET(w http.ResponseWriter, r *http.Request) { + ms := h.MS.Copy() + defer ms.Close() + + c := ms.DB(h.DB).C(h.C) + + if !bsonRE.MatchString(r.URL.Path) { + //all + dv := []model.Post{} + if e := c.Find(nil).All(&dv); e != nil { + http.Error(w, e.Error(), 500) + return + } + enc := json.NewEncoder(w) + if e := enc.Encode(dv); e != nil { + http.Error(w, e.Error(), 500) + return + } + } else { + //one + dv := model.Post{} + if e := c.FindId(bson.ObjectIdHex(r.URL.Path)).One(&dv); e != nil { + http.Error(w, e.Error(), 500) + return + } + enc := json.NewEncoder(w) + if e := enc.Encode(dv); e != nil { + http.Error(w, e.Error(), 500) + return + } + } +} + +func (h *PostHandler) POST(w http.ResponseWriter, r *http.Request) { + ms := h.MS.Copy() + defer ms.Close() + + c := ms.DB(h.DB).C(h.C) + + dv := new(model.Post) + dec := json.NewDecoder(r.Body) + if e := dec.Decode(dv); e != nil { + http.Error(w, e.Error(), 400) + return + } + if e := c.UpdateId(dv.Id, dv); e != nil { + http.Error(w, e.Error(), 400) + return + } + w.WriteHeader(200) +} + +func (h *PostHandler) PUT(w http.ResponseWriter, r *http.Request) { + ms := h.MS.Copy() + defer ms.Close() + + c := ms.DB(h.DB).C(h.C) + + dv := new(model.Post) + dec := json.NewDecoder(r.Body) + if e := dec.Decode(dv); e != nil { + http.Error(w, e.Error(), 400) + return + } + dv.Date = time.Now() + if e := c.Insert(dv); e != nil { + http.Error(w, e.Error(), 400) + return + } + w.WriteHeader(201) +} + +func (h *PostHandler) DELETE(w http.ResponseWriter, r *http.Request) { + if !bsonRE.MatchString(r.URL.Path) { + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return + } + ms := h.MS.Copy() + defer ms.Close() + + c := ms.DB(h.DB).C(h.C) + if e := c.RemoveId(bson.ObjectIdHex(r.URL.Path)); e != nil { + http.Error(w, e.Error(), 500) + } + w.WriteHeader(200) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..0840d2d --- /dev/null +++ b/main.go @@ -0,0 +1,28 @@ +package main + +import ( + "log" + "net/http" + + "gopkg.in/mgo.v2" + + "github.com/dalu/voce/handler" +) + +func main() { + ms, e := mgo.Dial("localhost") + if e != nil { + log.Fatalln(e.Error()) + } + defer ms.Close() + + ph := &handler.PostHandler{ + MS: ms.Clone(), + DB: "voce", + C: "posten", + } + defer ph.MS.Close() + + http.Handle("/post/", http.StripPrefix("/post/", ph)) + http.ListenAndServe(":8080", nil) +} diff --git a/model/rechnung.go b/model/rechnung.go new file mode 100644 index 0000000..3ecfa19 --- /dev/null +++ b/model/rechnung.go @@ -0,0 +1,14 @@ +package model + +import ( + "time" + + "gopkg.in/mgo.v2/bson" +) + +type Post struct { + Id bson.ObjectId `bson:"_id,omitempty" json:"id"` + Date time.Time `json:"date"` + Amount float64 `json:"amount"` + Note string `json:"note"` +} diff --git a/nginx/voce.dev.luketic.conf b/nginx/voce.dev.luketic.conf new file mode 100644 index 0000000..164cdde --- /dev/null +++ b/nginx/voce.dev.luketic.conf @@ -0,0 +1,22 @@ +server { + listen 80; + server_name voce.dev.luketic; + error_log /var/log/nginx/voce_error.log; + + # pass the request to the node.js server with the correct headers and much more can be added, see nginx config options + location /api/v1/ { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_set_header X-NginX-Proxy true; + + proxy_pass http://127.0.0.1:8080/; + proxy_redirect off; + } + + location / { + root /home/darko/go/src/github.com/dalu/voce/static/; + try_files $uri $uri/ /index.html =404; + } +} + diff --git a/static/bower.json b/static/bower.json new file mode 100644 index 0000000..3f2d05a --- /dev/null +++ b/static/bower.json @@ -0,0 +1,22 @@ +{ + "name": "voce", + "version": "0.0.0", + "authors": [ + "Darko Luketic " + ], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "dependencies": { + "angular": "~1.4.6", + "angular-bootstrap": "~0.13.4", + "angular-timeline": "~1.5.2", + "angular-ui-router": "~0.2.15", + "bootstrap": "~3.3.5" + } +} diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..e0af8a5 --- /dev/null +++ b/static/index.html @@ -0,0 +1,42 @@ + + + + + + + + + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/static/js/main.js b/static/js/main.js new file mode 100644 index 0000000..048f933 --- /dev/null +++ b/static/js/main.js @@ -0,0 +1,106 @@ +angular.module('voce',[ + 'ui.router', + 'ui.bootstrap', + 'angular-timeline' +]) + .config(['$stateProvider', '$urlRouterProvider', function ($stateProvider,$urlRouterProvider) { + $urlRouterProvider.otherwise('/'); + $stateProvider + .state('home',{ + url: '/', + templateUrl: '/views/home.html', + controller: "HomeCtrl" + }) + .state('revenue',{ + url: '/revenue', + templateUrl: '/views/revenue.html', + controller: 'RevenueCtrl' + }) + .state('expense',{ + url: '/expense', + templateUrl: '/views/expense.html', + controller: 'ExpenseCtrl' + }) + ; + }]) + .controller('HomeCtrl', ['$scope','$http','$state', function($scope,$http,$state) { + $scope.events = []; + $scope.income = {}; + $scope.expense = {}; + + $scope.refreshValues = function () { + $http.get('/api/v1/post/') + .success(function (data) { + angular.forEach(data, function (val, key) { + if (val.amount > 0) { + $scope.events.push({ + badgeClass: 'success', + badgeIconClass: 'glyphicon-plus', + title: val.amount, + content: val.note + }); + } else { + $scope.events.push({ + badgeClass: 'danger', + badgeIconClass: 'glyphicon-minus', + title: val.amount, + content: val.note + }); + } + }) + }); + }; + + $scope.submitIncome = function () { + $scope.income.amount *= 1.0; + $http.put('/api/v1/post/', $scope.income); + $scope.income = {}; + $state.go($state.current, {}, {reload: true}); + }; + $scope.submitExpense = function () { + $scope.expense.amount *= -1.0; + $http.put('/api/v1/post/', $scope.expense); + $scope.expense = {}; + $state.go($state.current, {}, {reload: true}); + }; + + $scope.refreshValues(); + }]) + .controller('RevenueCtrl',['$scope','$http', function ($scope, $http) { + $scope.revenues = []; + $scope.sum = 0.0; + + $http.get('/api/v1/post/') + .success(function (data) { + angular.forEach(data, function (val, key) { + if (val.amount > 0) { + $scope.revenues.push(val); + $scope.sum += val.amount; + } + }); + }); + + $scope.submitIncome = function () { + $scope.income.amount *= 1.0; + $http.put('/api/v1/post/', $scope.income); + $scope.income = {}; + $state.go($state.current, {}, {reload: true}); + }; + + + }]) + .controller('ExpenseCtrl',['$scope','$http', function ($scope, $http) { + $scope.expenses = []; + $scope.sum = 0.0; + $http.get('/api/v1/post/') + .success(function (data) { + angular.forEach(data, function (val, key) { + if (val.amount < 0) { + $scope.expenses.push(val); + $scope.sum += val.amount; + } + }); + }); + }]) + +; \ No newline at end of file diff --git a/static/views/expense.html b/static/views/expense.html new file mode 100644 index 0000000..7fb01ea --- /dev/null +++ b/static/views/expense.html @@ -0,0 +1,36 @@ +
+
+
+

{{sum|currency:"€"}}

+
+
+
+
+
+
+ Neue Einnahme +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
{{expense.date|date}}
+
+ {{expense.amount|currency:" € "}} +
+ +
+
+
+
\ No newline at end of file diff --git a/static/views/home.html b/static/views/home.html new file mode 100644 index 0000000..b40e18f --- /dev/null +++ b/static/views/home.html @@ -0,0 +1,55 @@ +
+
+
+
+
+ Neue Einnahme +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+ Neue Ausgabe +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+ + + + + + + +

{{event.title}}

+
+

{{event.content}}

+
+
+
+ +
+
+
diff --git a/static/views/revenue.html b/static/views/revenue.html new file mode 100644 index 0000000..12b610b --- /dev/null +++ b/static/views/revenue.html @@ -0,0 +1,36 @@ +
+
+
+

{{sum|currency:"€"}}

+
+
+
+
+
+
+ Neue Einnahme +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
{{revenue.date|date}}
+
+ {{revenue.amount|currency:" € "}} +
+ +
+
+
+
\ No newline at end of file