This commit is contained in:
Darko Luketic 2015-09-24 18:42:56 +02:00
commit dd0e8f6bf4
11 changed files with 486 additions and 0 deletions

5
handler/common.go Normal file
View File

@ -0,0 +1,5 @@
package handler
import "regexp"
var bsonRE = regexp.MustCompile(`[a-fA-F0-9]{24}`)

120
handler/posten.go Normal file
View File

@ -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)
}

28
main.go Normal file
View File

@ -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)
}

14
model/rechnung.go Normal file
View File

@ -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"`
}

View File

@ -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;
}
}

22
static/bower.json Normal file
View File

@ -0,0 +1,22 @@
{
"name": "voce",
"version": "0.0.0",
"authors": [
"Darko Luketic <info@icod.de>"
],
"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"
}
}

42
static/index.html Normal file
View File

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en" ng-app="voce">
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="bower_components/angular-timeline/dist/angular-timeline.css"/>
</head>
<body>
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" ui-sref="home">Voce</a>
</div>
<div class="collapse navbar-collapse" id="navbar-collapse">
<ul class="nav navbar-nav">
<li ui-sref-active="active"><a ui-sref="home">Übersicht</a></li>
<li ui-sref-active="active"><a ui-sref="revenue">Einnahmen</a></li>
<li ui-sref-active="active"><a ui-sref="expense">Ausgaben</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div ui-view></div>
<script src="bower_components/jquery/dist/jquery.min.js"></script>
<script src="bower_components/angular/angular.min.js"></script>
<script src="bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
<script src="bower_components/angular-ui-router/release/angular-ui-router.min.js"></script>
<script src="bower_components/angular-bootstrap/ui-bootstrap.min.js"></script>
<script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>
<script src="bower_components/angular-timeline/dist/angular-timeline.js"></script>
<script src="js/main.js"></script>
</body>
</html>

106
static/js/main.js Normal file
View File

@ -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;
}
});
});
}])
;

36
static/views/expense.html Normal file
View File

@ -0,0 +1,36 @@
<div class="container">
<div class="row">
<div class="col-lg-12">
<h1>{{sum|currency:"€"}}</h1>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<form ng-submit="submitIncome()">
<fieldset>
<legend>Neue Einnahme</legend>
</fieldset>
<div class="form-group">
<input class="form-control" placeholder="Betrag" ng-model="income.amount">
</div>
<div class="form-group">
<textarea class="form-control" placeholder="Notizen" ng-model="income.note"></textarea>
</div>
<div class="form-group">
<button class="btn btn-block">Einname Speichern</button>
</div>
</form>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default" ng-repeat="expense in expenses">
<div class="panel-heading">{{expense.date|date}}</div>
<div class="panel-body">
{{expense.amount|currency:" € "}}
</div>
<div class="panel-footer">{{expense.note}}</div>
</div>
</div>
</div>
</div>

55
static/views/home.html Normal file
View File

@ -0,0 +1,55 @@
<div class="container">
<div class="row">
<div class="col-lg-6">
<form ng-submit="submitIncome()">
<fieldset>
<legend>Neue Einnahme</legend>
</fieldset>
<div class="form-group">
<input class="form-control" placeholder="Betrag" ng-model="income.amount">
</div>
<div class="form-group">
<textarea class="form-control" placeholder="Notizen" ng-model="income.note"></textarea>
</div>
<div class="form-group">
<button class="btn btn-block">Einname Speichern</button>
</div>
</form>
</div>
<div class="col-lg-6">
<form ng-submit="submitExpense()">
<fieldset>
<legend>Neue Ausgabe</legend>
</fieldset>
<div class="form-group">
<input class="form-control" placeholder="Betrag" ng-model="expense.amount">
</div>
<div class="form-group">
<textarea class="form-control" placeholder="Notizen" ng-model="expense.note"></textarea>
</div>
<div class="form-group">
<button class="btn btn-block">Ausgabe Speichern</button>
</div>
</form>
</div>
</div>
<hr>
<div class="row">
<div class="col-lg-12">
<timeline>
<timeline-event ng-repeat="event in events">
<timeline-badge class="{{event.badgeClass}}">
<i class="glyphicon {{event.badgeIconClass}}"></i>
</timeline-badge>
<timeline-panel class="{{event.badgeClass}}">
<timeline-heading>
<h4>{{event.title}}</h4>
</timeline-heading>
<p>{{event.content}}</p>
</timeline-panel>
</timeline-event>
</timeline>
</div>
</div>
</div>

36
static/views/revenue.html Normal file
View File

@ -0,0 +1,36 @@
<div class="container">
<div class="row">
<div class="col-lg-12">
<h1>{{sum|currency:"€"}}</h1>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<form ng-submit="submitIncome()">
<fieldset>
<legend>Neue Einnahme</legend>
</fieldset>
<div class="form-group">
<input class="form-control" placeholder="Betrag" ng-model="income.amount">
</div>
<div class="form-group">
<textarea class="form-control" placeholder="Notizen" ng-model="income.note"></textarea>
</div>
<div class="form-group">
<button class="btn btn-block">Einname Speichern</button>
</div>
</form>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default" ng-repeat="revenue in revenues">
<div class="panel-heading">{{revenue.date|date}}</div>
<div class="panel-body">
{{revenue.amount|currency:" € "}}
</div>
<div class="panel-footer">{{revenue.note}}</div>
</div>
</div>
</div>
</div>