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