initial
This commit is contained in:
commit
dd0e8f6bf4
5
handler/common.go
Normal file
5
handler/common.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import "regexp"
|
||||||
|
|
||||||
|
var bsonRE = regexp.MustCompile(`[a-fA-F0-9]{24}`)
|
120
handler/posten.go
Normal file
120
handler/posten.go
Normal 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
28
main.go
Normal 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
14
model/rechnung.go
Normal 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"`
|
||||||
|
}
|
22
nginx/voce.dev.luketic.conf
Normal file
22
nginx/voce.dev.luketic.conf
Normal 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
22
static/bower.json
Normal 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
42
static/index.html
Normal 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
106
static/js/main.js
Normal 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
36
static/views/expense.html
Normal 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
55
static/views/home.html
Normal 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
36
static/views/revenue.html
Normal 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>
|
Loading…
Reference in New Issue
Block a user