init meetandeat

This commit is contained in:
Martin/Geno 2019-03-22 16:35:16 +01:00
commit c6c983820e
No known key found for this signature in database
GPG Key ID: 9D7D3C6BFF600C6A
44 changed files with 3780 additions and 0 deletions

8
.ci/check-gofmt Executable file
View File

@ -0,0 +1,8 @@
#!/bin/bash
result="$(gofmt -s -l . | grep -v '^vendor/' )"
if [ -n "$result" ]; then
echo "Go code is not formatted, run 'gofmt -s -w .'" >&2
echo "$result"
exit 1
fi

25
.ci/check-testfiles Executable file
View File

@ -0,0 +1,25 @@
#!/usr/bin/env python
# checks if every desired package has test files
import os
import re
import sys
source_re = re.compile(".*\.go")
test_re = re.compile(".*_test\.go")
missing = False
for root, dirs, files in os.walk("."):
# ignore some paths
if root == "." or root.startswith("./vendor") or root.startswith("./."):
continue
# source files but not test files?
if len(filter(source_re.match, files)) > 0 and len(filter(test_re.match, files)) == 0:
print("no test files for {}".format(root))
missing = True
if missing:
sys.exit(1)
else:
print("every package has test files")

53
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,53 @@
image: golang:latest
stages:
- build
- test
- deploy
before_script:
- mkdir -p "/go/src/dev.sum7.eu/$CI_PROJECT_NAMESPACE/"
- cp -R "/builds/$CI_PROJECT_PATH" "/go/src/dev.sum7.eu/$CI_PROJECT_NAMESPACE/"
- cd "/go/src/dev.sum7.eu/$CI_PROJECT_PATH"
- go get -d -t ./...
build-my-project:
stage: build
script:
- go install "dev.sum7.eu/$CI_PROJECT_PATH"
- mv "/go/bin/$CI_PROJECT_NAME" "/builds/$CI_PROJECT_PATH"
artifacts:
paths:
- config_example.conf
- "$CI_PROJECT_NAME"
test-my-project:
stage: test
script:
- go get github.com/client9/misspell/cmd/misspell
- find . -type f | grep -v webroot/assets | xargs misspell -error
- ./.ci/check-gofmt
- ./.ci/check-testfiles
- go test $(go list ./... | grep -v /vendor/) -v -coverprofile .testCoverage.txt
artifacts:
paths:
- .testCoverage.txt
test-race-my-project:
stage: test
script:
- go test -race ./...
deploy:
stage: deploy
only:
- master
script:
- go install "dev.sum7.eu/$CI_PROJECT_PATH"
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
- 'which rsync || ( apt-get update -y && apt-get install rsync -y )'
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
- rsync -e "ssh -6 -o StrictHostKeyChecking=no -p $SSH_PORT" -a --delete "/builds/$CI_PROJECT_PATH/webroot/" "$CI_PROJECT_NAME@$SSH_HOST":/opt/$CI_PROJECT_NAME/webroot/
- ssh -6 -o StrictHostKeyChecking=no -p $SSH_PORT "$CI_PROJECT_NAME@$SSH_HOST" sudo /usr/bin/systemctl stop $CI_PROJECT_NAME
- scp -6 -o StrictHostKeyChecking=no -P $SSH_PORT "/go/bin/$CI_PROJECT_NAME" "$CI_PROJECT_NAME@$SSH_HOST":/opt/$CI_PROJECT_NAME/bin
- ssh -6 -o StrictHostKeyChecking=no -p $SSH_PORT "$CI_PROJECT_NAME@$SSH_HOST" sudo /usr/bin/systemctl start $CI_PROJECT_NAME

47
README.md Normal file
View File

@ -0,0 +1,47 @@
# meetandeat
[![Build Status](https://dev.sum7.eu/genofire/meetandeat/badges/master/build.svg)](https://dev.sum7.eu/genofire/meetandeat/pipelines)
[![Go Report Card](https://goreportcard.com/badge/dev.sum7.eu/genofire/meetandeat)](https://goreportcard.com/report/dev.sum7.eu/genofire/meetandeat)
[![GoDoc](https://godoc.org/dev.sum7.eu/genofire/meetandeat?status.svg)](https://godoc.org/dev.sum7.eu/genofire/meetandeat)
## Get meetandeat
#### Download
Latest Build binary from ci here:
[Download All](https://dev.sum7.eu/genofire/meetandeat/-/jobs/artifacts/master/download/?job=build-my-project) (with config example)
[Download Binary](https://dev.sum7.eu/genofire/meetandeat/-/jobs/artifacts/master/raw/meetandeat?inline=false&job=build-my-project)
#### Build
```bash
go get -u dev.sum7.eu/genofire/meetandeat
```
## Configure
see `config_example.conf`
## Start / Boot
_/lib/systemd/system/meetandeat.service_ :
```
[Unit]
Description=meetandeat
After=network.target
[Service]
Type=simple
# User=notRoot
ExecStart=/opt/go/bin/meetandeat serve --config /etc/meetandeat.conf
Restart=always
RestartSec=5sec
[Install]
WantedBy=multi-user.target
```
Start: `systemctl start meetandeat`
Autostart: `systemctl enable meetandeat`

38
cmd/root.go Normal file
View File

@ -0,0 +1,38 @@
package cmd
import (
"github.com/bdlm/log"
"github.com/spf13/cobra"
)
var (
debug bool
timestamps bool
)
// RootCMD represents the base command when called without any subcommands
var RootCMD = &cobra.Command{
Use: "meetandeat",
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the RootCMD.
func Execute() {
if err := RootCMD.Execute(); err != nil {
log.Error(err)
}
}
func init() {
cobra.OnInitialize(func() {
if debug {
log.SetLevel(log.DebugLevel)
}
log.SetFormatter(&log.TextFormatter{
DisableTimestamp: timestamps,
})
log.Debug("show debug")
})
RootCMD.PersistentFlags().BoolVar(&debug, "v", false, "show debug log")
RootCMD.PersistentFlags().BoolVar(&timestamps, "timestamps", false, "Enables timestamps for log output")
}

1
cmd/root_test.go Normal file
View File

@ -0,0 +1 @@
package cmd

87
cmd/serve.go Normal file
View File

@ -0,0 +1,87 @@
package cmd
import (
"dev.sum7.eu/genofire/golang-lib/database"
"dev.sum7.eu/genofire/golang-lib/file"
"github.com/bdlm/log"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-contrib/static"
"github.com/gin-gonic/autotls"
"github.com/gin-gonic/gin"
"github.com/spf13/cobra"
"golang.org/x/crypto/acme/autocert"
"dev.sum7.eu/genofire/meetandeat/controller"
"dev.sum7.eu/genofire/meetandeat/runtime"
)
var configPath = "/etc/meetandeat.conf"
type ServeConfig struct {
RequestLog bool `toml:"request_log"`
RequestLimit int `toml:"request_limit"`
SessionSecret string `toml:"session_secret"`
Listen string `toml:"listen"`
Webroot string `toml:"webroot"`
Database database.Config `toml:"database"`
ACME struct {
Enable bool `toml:"enable"`
Domains []string `toml:"domains"`
Cache string `toml:"cache"`
} `toml:"acme"`
}
// serveCMD represents the query command
var serveCMD = &cobra.Command{
Use: "serve <interfaces>",
Short: "run webserver",
Example: `meetandeat serve "/etc/meetandeat.conf"`,
Run: func(cmd *cobra.Command, args []string) {
config := &ServeConfig{}
if err := file.ReadTOML(configPath, config); err != nil {
log.Panicf("open config file: %s", err)
}
if err := database.Open(config.Database); err != nil {
log.Panicf("no database connection: %s", err)
}
gin.SetMode(gin.ReleaseMode)
r := gin.New()
if config.RequestLog {
r.Use(gin.Logger())
log.Debug("request logging enabled")
}
r.Use(runtime.MaxAllowed(config.RequestLimit))
r.Use(gin.Recovery())
r.Use(sessions.Sessions("sid", cookie.NewStore([]byte(config.SessionSecret))))
controller.Bind(&r.RouterGroup)
r.Use(static.Serve("/", static.LocalFile(config.Webroot, false)))
if config.ACME.Enable {
if config.Listen != "" {
log.Panic("For ACME / Let's Encrypt it is not possible to set `listen`")
}
m := autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(config.ACME.Domains...),
Cache: autocert.DirCache(config.ACME.Cache),
}
log.Fatal(autotls.RunWithManager(r, &m))
} else {
r.Run(config.Listen)
}
},
}
func init() {
serveCMD.PersistentFlags().StringVar(&configPath, "config", configPath, "path to config file")
RootCMD.AddCommand(serveCMD)
}

22
config_example.conf Normal file
View File

@ -0,0 +1,22 @@
log_level = 50
request_log = true
request_limit = 10
session_secret = "secret"
webroot = "webroot"
listen = ":8080"
# if ACME / Let's Encrypt used, uncomment listen
[acme]
enable = false
domains = ["example1.com", "example2.com"]
cache = "/var/www/.cache"
[database]
type = "sqlite3"
logging = true
connection = "file::memory:?mode=memory&cache=shared"
# For Master-Slave cluster
# read_connection = ""

81
controller/api/date.go Normal file
View File

@ -0,0 +1,81 @@
package api
import (
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
"dev.sum7.eu/genofire/golang-lib/database"
"dev.sum7.eu/genofire/meetandeat/models"
)
func getDateAll(c *gin.Context) {
list := []*models.Date{}
database.Read.Find(&list)
c.JSON(http.StatusOK, list)
}
func getDate(c *gin.Context) {
id, err := strconv.Atoi(c.Params.ByName("id"))
if err != nil {
c.JSON(http.StatusBadRequest, err.Error())
return
}
d := models.Date{
ID: id,
}
res := database.Read.Preload("Meets").First(&d)
if res.RecordNotFound() {
c.JSON(http.StatusNotFound, res.Error.Error())
return
}
if res.Error != nil {
c.JSON(http.StatusBadRequest, res.Error.Error())
return
}
c.JSON(http.StatusOK, d)
}
func addDate(c *gin.Context) {
var v time.Time
if err := c.BindJSON(&v); err != nil {
c.JSON(http.StatusBadRequest, err.Error())
return
}
if v.Before(time.Now()) {
c.JSON(http.StatusBadRequest, "date has to be in the future")
return
}
d := &models.Date{
Date: v,
}
database.Write.Save(d)
c.JSON(http.StatusOK, d)
}
func addDateMeet(c *gin.Context) {
var v models.DateMeet
if err := c.BindJSON(&v); err != nil {
c.JSON(http.StatusBadRequest, err.Error())
return
}
id, err := strconv.Atoi(c.Params.ByName("id"))
if err != nil {
c.JSON(http.StatusBadRequest, err.Error())
return
}
v.DateID = id
database.Write.Save(&v)
c.JSON(http.StatusOK, v)
}

View File

@ -0,0 +1,3 @@
package api
// Nothing to test

33
controller/api/job.go Normal file
View File

@ -0,0 +1,33 @@
package api
import (
"net/http"
"github.com/gin-gonic/gin"
"dev.sum7.eu/genofire/golang-lib/database"
"dev.sum7.eu/genofire/meetandeat/models"
)
func getJobAll(c *gin.Context) {
list := []*models.Job{}
database.Read.Find(&list)
c.JSON(http.StatusOK, list)
}
func addJob(c *gin.Context) {
var name string
if err := c.BindJSON(&name); err != nil {
c.JSON(http.StatusBadRequest, err.Error())
return
}
d := &models.Job{
Name: name,
}
database.Write.Save(d)
c.JSON(http.StatusOK, d)
}

19
controller/api/main.go Normal file
View File

@ -0,0 +1,19 @@
package api
import (
"github.com/gin-gonic/gin"
)
func Bind(r *gin.RouterGroup) {
r.GET("/date", getDateAll)
r.POST("/date", addDate)
r.GET("/date/:id", getDate)
r.POST("/date/:id/meet", addDateMeet)
r.GET("/job", getJobAll)
r.POST("/job", addJob)
r.GET("/person", getPersonAll)
r.POST("/person", addPerson)
}

33
controller/api/person.go Normal file
View File

@ -0,0 +1,33 @@
package api
import (
"net/http"
"github.com/gin-gonic/gin"
"dev.sum7.eu/genofire/golang-lib/database"
"dev.sum7.eu/genofire/meetandeat/models"
)
func getPersonAll(c *gin.Context) {
list := []*models.Person{}
database.Read.Find(&list)
c.JSON(http.StatusOK, list)
}
func addPerson(c *gin.Context) {
var name string
if err := c.BindJSON(&name); err != nil {
c.JSON(http.StatusBadRequest, err.Error())
return
}
d := &models.Person{
Name: name,
}
database.Write.Save(d)
c.JSON(http.StatusOK, d)
}

3
controller/docs_test.go Normal file
View File

@ -0,0 +1,3 @@
package controller
// Nothing to test

View File

@ -0,0 +1,3 @@
package examples
// Nothing to test

View File

@ -0,0 +1,23 @@
package examples
import (
"net/http"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
)
func incr(c *gin.Context) {
session := sessions.Default(c)
var count int
v := session.Get("count")
if v == nil {
count = 0
} else {
count = v.(int)
count++
}
session.Set("count", count)
session.Save()
c.JSON(http.StatusOK, gin.H{"count": count})
}

View File

@ -0,0 +1,11 @@
package examples
import (
"github.com/gin-gonic/gin"
)
func Bind(r *gin.RouterGroup) {
r.GET("/ping", ping)
r.GET("/incr", incr)
}

View File

@ -0,0 +1,11 @@
package examples
import (
"net/http"
"github.com/gin-gonic/gin"
)
func ping(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "pong"})
}

21
controller/main.go Normal file
View File

@ -0,0 +1,21 @@
package controller
import (
"github.com/gin-gonic/gin"
"dev.sum7.eu/genofire/meetandeat/controller/api"
"dev.sum7.eu/genofire/meetandeat/controller/examples"
)
func Bind(r *gin.RouterGroup) {
group := r.Group("/examples")
{
examples.Bind(group)
}
group = r.Group("/api")
{
api.Bind(group)
}
r.GET("/status", status)
}

18
controller/status.go Normal file
View File

@ -0,0 +1,18 @@
package controller
import (
"github.com/gin-gonic/gin"
"net/http"
"dev.sum7.eu/genofire/golang-lib/database"
)
func status(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"status": "running",
"database": map[string]interface{}{
"read": database.Read.DB().Ping() == nil,
"write": database.Write.DB().Ping() == nil,
},
})
}

7
main.go Normal file
View File

@ -0,0 +1,7 @@
package main
import "dev.sum7.eu/genofire/meetandeat/cmd"
func main() {
cmd.Execute()
}

18
models/date.go Normal file
View File

@ -0,0 +1,18 @@
package models
import (
"time"
"dev.sum7.eu/genofire/golang-lib/database"
)
type Date struct {
ID int `json:"id" gorm:"PRIMARY_KEY"`
Date time.Time `json:"date"`
Meets []DateMeet `gorm:"foreignkey:ID" json:"meets"`
}
// Function to initialize the database
func init() {
database.AddModel(&Date{})
}

20
models/datemeets.go Normal file
View File

@ -0,0 +1,20 @@
package models
import (
"dev.sum7.eu/genofire/golang-lib/database"
)
type DateMeet struct {
ID int `gorm:"PRIMARY_KEY" json:"id"`
DateID int `sql:"type:bigint NOT NULL REFERENCES date(id);column:date_id" json:"dateID,omitempty"`
Date *Date `gorm:"association_autoupdate:false;foreignkey:DateID;" json:"date,omitempty"`
PersonID int `sql:"type:bigint NOT NULL REFERENCES person(id);column:person_id" json:"personID,omitempty"`
Person *Person `gorm:"association_autoupdate:false;foreignkey:PersonID;" json:"person,omitempty"`
JobID int `sql:"type:bigint NOT NULL REFERENCES job(id);column:job_id" json:"jobID,omitempty"`
Job *Job `gorm:"association_autoupdate:false;foreignkey:JobID;" json:"job,omitempty"`
}
// Function to initialize the database
func init() {
database.AddModel(&DateMeet{})
}

1
models/doc_test.go Normal file
View File

@ -0,0 +1 @@
package models

15
models/job.go Normal file
View File

@ -0,0 +1,15 @@
package models
import (
"dev.sum7.eu/genofire/golang-lib/database"
)
type Job struct {
ID int `json:"id" gorm:"PRIMARY_KEY"`
Name string `json:"name"`
}
// Function to initialize the database
func init() {
database.AddModel(&Job{})
}

15
models/person.go Normal file
View File

@ -0,0 +1,15 @@
package models
import (
"dev.sum7.eu/genofire/golang-lib/database"
)
type Person struct {
ID int `json:"id" gorm:"PRIMARY_KEY"`
Name string `json:"name"`
}
// Function to initialize the database
func init() {
database.AddModel(&Person{})
}

3
runtime/docs_test.go Normal file
View File

@ -0,0 +1,3 @@
package runtime
// Nothing to test

61
runtime/hash.go Normal file
View File

@ -0,0 +1,61 @@
package runtime
import "golang.org/x/crypto/pbkdf2"
import "hash"
import "strconv"
import "encoding/base64"
import "crypto/sha1"
import "crypto/sha256"
import "crypto/sha512"
import "crypto/rand"
import "fmt"
import "strings"
const (
saltLength = 8
hashLength = 20
interations = 10000
hashfunc string = "sha256"
)
var hashlib = map[string]func() hash.Hash{
"sha1": sha1.New,
"sha256": sha256.New,
"sha512": sha512.New,
}
// HashValidate a password and a hash
func HashValidate(hash, password string) (output, replace bool) {
parts := strings.Split(hash, "$")
if len(parts) != 4 {
return false, false
}
curIter, err := strconv.Atoi(parts[1])
if err != nil {
return false, false
}
hashfuncC := strings.Split(parts[0], "_")[1]
replace = (hashfuncC != hashfunc)
dk := pbkdf2.Key([]byte(password), []byte(parts[2]), curIter, len(parts[3])-8, hashlib[hashfuncC])
x := fmt.Sprintf("pbkdf2_%s$%s$%s$%s", hashfuncC, parts[1], parts[2], base64.StdEncoding.EncodeToString(dk))
output = (x == hash)
return
}
// GenerateRandomString by length for key
func GenerateRandomString(n int) (string, error) {
b := make([]byte, n)
_, err := rand.Read(b)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b), nil
}
// NewHash of given password
func NewHash(password string) string {
salt, _ := GenerateRandomString(saltLength)
dk := pbkdf2.Key([]byte(password), []byte(salt), interations, hashLength, hashlib[hashfunc])
return fmt.Sprintf("pbkdf2_%s$%d$%s$%s", hashfunc, interations, salt, base64.StdEncoding.EncodeToString(dk))
}

23
runtime/hash_test.go Normal file
View File

@ -0,0 +1,23 @@
package runtime
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestHash(t *testing.T) {
assert := assert.New(t)
password := "root"
x, err := HashValidate("pbkdf2_sha1$10000$a5viM+Paz3o=$orD4shu1Ss+1wPAhAt8hkZ/fH7Y=", password)
assert.True(x, "password is valid")
assert.True(err, "password hesh is outdated")
x, err = HashValidate("pbkdf2_sha256$10000$TnTxDT6fLiE=$v3EGDGIgmgSWXaLYvQYlSf8Lob0=", password)
assert.True(x, "password is valid")
assert.False(err, "password hash is outdated")
hashed := NewHash(password)
expectedBegin := "pbkdf2_sha256$10000$"
assert.Equal(expectedBegin, hashed[:len(expectedBegin)])
}

17
runtime/limit.go Normal file
View File

@ -0,0 +1,17 @@
package runtime
import (
"github.com/gin-gonic/gin"
)
func MaxAllowed(n int) gin.HandlerFunc {
sem := make(chan struct{}, n)
acquire := func() { sem <- struct{}{} }
release := func() { <-sem }
return func(c *gin.Context) {
acquire() // before request
defer release() // after request
c.Next()
}
}

4
webroot/assets/get_assets.sh Executable file
View File

@ -0,0 +1,4 @@
wget -O vanilla-framework.css https://assets.ubuntu.com/v1/vanilla-framework-version-1.8.1.min.css
wget -O vue.js https://vuejs.org/js/vue.min.js
wget -O vue-route.js https://unpkg.com/vue-router/dist/vue-router.js
wget -O vuex.js https://unpkg.com/vuex@2.0.0/dist/vuex.min.js

File diff suppressed because one or more lines are too long

2626
webroot/assets/vue-route.js Normal file

File diff suppressed because it is too large Load Diff

6
webroot/assets/vue.js Normal file

File diff suppressed because one or more lines are too long

6
webroot/assets/vuex.js Normal file

File diff suppressed because one or more lines are too long

4
webroot/css/main.css Normal file
View File

@ -0,0 +1,4 @@
.p-navigation__logo {
font-size: 1.5rem;
line-height: 3rem;
}

52
webroot/index.html Normal file
View File

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Wifictld</title>
<link rel="stylesheet" href="assets/vanilla-framework.css" />
<link rel="stylesheet" href="css/main.css" />
<script src="assets/vue.js"></script>
<script src="assets/vue-route.js"></script>
<script src="assets/vuex.js"></script>
<script src="js/utils.js"></script>
</head>
<body role="document">
<div id="app">
<header id="navigation" class="p-navigation">
<div class="p-navigation__logo">
<a class="p-navigation__link" href="/">Meet&amp;Eat</a>
</div>
<div class="p-navigation__banner">
<a href="#navigation" class="p-navigation__toggle--open" title="menu">Menu</a>
<a href="#navigation-closed" class="p-navigation__toggle--close" title="close menu">Close menu</a>
</div>
<nav class="p-navigation__nav" role="menubar">
<span class="u-off-screen">
<a href="#main-content">Jump to main content</a>
</span>
<ul class="p-navigation__links" role="menu">
<router-link :to="{name: 'dates' }" tag="li" class="p-navigation__link" active-class="is-selected">
<a>Termine</a>
</router-link>
<router-link :to="{name: 'person' }" tag="li" class="p-navigation__link" active-class="is-selected">
<a>Personen</a>
</router-link>
<router-link :to="{name: 'job' }" tag="li" class="p-navigation__link" active-class="is-selected">
<a>Jobs</a>
</router-link>
</ul>
</nav>
</header>
<div class="content">
<router-view></router-view>
</div>
</div>
<script src="js/view/date.js"></script>
<script src="js/view/dates.js"></script>
<script src="js/view/jobs.js"></script>
<script src="js/view/persons.js"></script>
<script src="js/view.js"></script>
<script src="js/main.js"></script>
</body>
</html>

4
webroot/js/main.js Normal file
View File

@ -0,0 +1,4 @@
const app = new Vue({
el: '#app',
router,
})

28
webroot/js/utils.js Normal file
View File

@ -0,0 +1,28 @@
function http(url,type,data) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
req.open(type, url);
req.onload = function onload() {
if (req.status === 200) {
resolve(req.response);
} else {
reject(Error(req.response));
}
};
req.onerror = function onerror() {
reject(Error('Network Error'));
};
req.send(data);
});
}
function httpJSON(url, type, data) {
return http(url, type, data).then(JSON.parse);
}
function get(url) {
return http(url,'GET');
}
function getJSON(url) {
return httpJSON(url, 'GET');
}

10
webroot/js/view.js Normal file
View File

@ -0,0 +1,10 @@
const router = new VueRouter({
routes: [
{ path: '/date', component: ViewDates, name: "dates" },
{ path: '/date/:id', component: ViewDate, name: "date.show"},
{ path: '/job', component: ViewJobs, name: "job" },
{ path: '/person', component: ViewPersons, name: "person" },
{ path: '/', redirect: '/date' }
]
})

71
webroot/js/view/date.js Normal file
View File

@ -0,0 +1,71 @@
const ViewDate = {
data () {
return {
form: false,
data: {},
}
},
created () {
self = this;
self.refresh()
},
watch: {
'$route': 'refresh'
},
methods: {
refresh() {
getJSON(`//${location.host}${location.pathname}api/date/`+self.$route.params.id).then(function(data){
self.data = data;
})
},
openAdd() {
self.form = true;
self.edit.id = 0;
},
send() {
if (self.edit.id === 0) {
httpJSON(`//${location.host}${location.pathname}api/date`,'POST', self.edit)
.then(function(data) {
self.form = false;
}, function (err) {
alert("unable to add new date:" + err.message);
})
}
},
},
template: `<div class="row">
<p>
<span class="p-heading--one">Termin: {{new Date(data.date).toDateString()}} </span>
<sub>{{$route.params.id}}</sub>
<div class="p-modal" v-if="form">
<div class="p-modal__dialog" role="dialog" aria-labelledby="modal-title" aria-describedby="modal-description">
<header class="p-modal__header">
<h2 class="p-modal__title" id="modal-title">Neuer Termin</h2>
<button class="p-modal__close" aria-label="Close active modal" v-on:click="form=false">Close</button>
</header>
<p id="modal-description">
<form class="p-form p-form--stacked">
<div class="p-form__group">
<label for="full-name-stacked" class="p-form__label">Date</label>
<div class="p-form__control">
<input type="date" v-model="edit.date" required>
</div>
</div>
<button class="p-button--positive u-float-right" v-on:click="send()">Add</button>
</form>
</p>
</div>
</div>
</p>
<table class="p-table--mobile-card" role="grid">
<thead>
<tr role="row">
<th scope="col" role="columnheader" aria-sort="none">Datum</th>
<th scope="col" role="columnheader" aria-sort="none" class="u-align--right">Personen</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>`,
}

88
webroot/js/view/dates.js Normal file
View File

@ -0,0 +1,88 @@
const ViewDates = {
data () {
return {
edit: {
id: 0,
},
form: false,
list: [],
}
},
created () {
self = this
self.refresh();
},
watch: {
'$route': 'refresh'
},
methods: {
refresh() {
getJSON(`//${location.host}${location.pathname}api/date`).then(function(data){
self.list = data;
})
},
openAdd() {
self.form = true;
self.edit.id = 0;
},
send() {
if (self.edit.id === 0) {
httpJSON(`//${location.host}${location.pathname}api/date`,'POST', '"' + new Date(self.edit.date).toISOString() + '"')
.then(function(data) {
self.refresh();
self.form = false;
}, function (err) {
alert("unable to add new date:" + err.message);
})
}
},
personen (date) {
return 0;
}
},
template: `<div class="row">
<p>
<span class="p-heading--one">Termine</span>
<sub>
<button v-on:click="openAdd()">New</button>
</sub>
<div class="p-modal" v-if="form">
<div class="p-modal__dialog" role="dialog" aria-labelledby="modal-title" aria-describedby="modal-description">
<header class="p-modal__header">
<h2 class="p-modal__title" id="modal-title">Neuer Termin</h2>
<button class="p-modal__close" aria-label="Close active modal" v-on:click="form=false">Close</button>
</header>
<p id="modal-description">
<form class="p-form p-form--stacked">
<div class="p-form__group">
<label for="full-name-stacked" class="p-form__label">Date</label>
<div class="p-form__control">
<input type="date" v-model="edit.date" required>
</div>
</div>
<button class="p-button--positive u-float-right" v-on:click="send()">Add</button>
</form>
</p>
</div>
</div>
</p>
<table class="p-table--mobile-card" role="grid">
<thead>
<tr role="row">
<th scope="col" role="columnheader" aria-sort="none">Datum</th>
<th scope="col" role="columnheader" aria-sort="none" class="u-align--right">Personen</th>
</tr>
</thead>
<tbody>
<tr role="row" v-for="item in list">
<td role="rowheader" aria-label="Datum">{{ new Date(item.date).toDateString() }}</td>
<td role="gridcell" aria-label="Personen" class="u-align--right">
<router-link :to="{ name: 'date.show', params: { id: item.id }}">
{{ personen(item) }}
</router-link>
</td>
</tr>
</tbody>
</table>
</div>`,
}

79
webroot/js/view/jobs.js Normal file
View File

@ -0,0 +1,79 @@
const ViewJobs = {
data () {
return {
edit: {
id: 0,
},
form: false,
list: [],
}
},
created () {
self = this;
self.refresh();
},
watch: {
'$route': 'refresh'
},
methods: {
refresh () {
getJSON(`//${location.host}${location.pathname}api/job`).then(function(data){
self.list = data;
})
},
openAdd() {
self.form = true;
self.edit.id = 0;
},
send() {
if (self.edit.id === 0) {
httpJSON(`//${location.host}${location.pathname}api/job`,'POST', '"' + self.edit.name + '"')
.then(function(data) {
self.refresh();
self.form = false;
}, function (err) {
alert("unable to add new job:" + err.message);
})
}
}
},
template: `<div class="row">
<p>
<span class="p-heading--one">Job</span>
<sub>
<button v-on:click="openAdd()">New</button>
</sub>
<div class="p-modal" v-if="form">
<div class="p-modal__dialog" role="dialog" aria-labelledby="modal-title" aria-describedby="modal-description">
<header class="p-modal__header">
<h2 class="p-modal__title" id="modal-title">Neuer Job</h2>
<button class="p-modal__close" aria-label="Close active modal" v-on:click="form=false">Close</button>
</header>
<p id="modal-description">
<form class="p-form p-form--stacked">
<div class="p-form__group">
<label class="p-form__label">Name/Bezeichnung</label>
<div class="p-form__control">
<input type="text" v-model="edit.name" required>
</div>
</div>
<button class="p-button--positive u-float-right" v-on:click="send()">Add</button>
</form>
</p>
</div>
</div>
</p>
<table class="p-table--mobile-card" role="grid">
<thead>
<tr role="row">
<th scope="col" role="columnheader" aria-sort="none">Name/Bezeichnung</th>
</tr>
</thead>
<tbody>
<tr role="row" v-for="item in list">
<td role="rowheader" aria-label="Name/Bezeichnung">{{ item.name }}</td>
</tr>
</tbody>
</table>
</div>`,
}

View File

@ -0,0 +1,79 @@
const ViewPersons = {
data () {
return {
edit: {
id: 0,
},
form: false,
list: [],
}
},
created () {
self = this;
self.refresh()
},
watch: {
'$route': 'refresh'
},
methods: {
refresh () {
getJSON(`//${location.host}${location.pathname}api/person`).then(function(data){
self.list = data;
})
},
openAdd() {
self.form = true;
self.edit.id = 0;
},
send() {
if (self.edit.id === 0) {
httpJSON(`//${location.host}${location.pathname}api/person`,'POST', '"' + self.edit.name + '"')
.then(function(data) {
self.refresh();
self.form = false;
}, function (err) {
alert("unable to add new person:" + err.message);
})
}
}
},
template: `<div class="row">
<p>
<span class="p-heading--one">Person</span>
<sub>
<button v-on:click="openAdd()">New</button>
</sub>
<div class="p-modal" v-if="form">
<div class="p-modal__dialog" role="dialog" aria-labelledby="modal-title" aria-describedby="modal-description">
<header class="p-modal__header">
<h2 class="p-modal__title" id="modal-title">Neue Person</h2>
<button class="p-modal__close" aria-label="Close active modal" v-on:click="form=false">Close</button>
</header>
<p id="modal-description">
<form class="p-form p-form--stacked">
<div class="p-form__group">
<label class="p-form__label">Name</label>
<div class="p-form__control">
<input type="text" v-model="edit.name" required>
</div>
</div>
<button class="p-button--positive u-float-right" v-on:click="send()">Add</button>
</form>
</p>
</div>
</div>
</p>
<table class="p-table--mobile-card" role="grid">
<thead>
<tr role="row">
<th scope="col" role="columnheader" aria-sort="none">Name</th>
</tr>
</thead>
<tbody>
<tr role="row" v-for="item in list">
<td role="rowheader" aria-label="Name">{{ item.name }}</td>
</tr>
</tbody>
</table>
</div>`,
}