init meetandeat
This commit is contained in:
commit
c6c983820e
|
@ -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
|
|
@ -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")
|
|
@ -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
|
|
@ -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`
|
|
@ -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(×tamps, "timestamps", false, "Enables timestamps for log output")
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package cmd
|
|
@ -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)
|
||||
}
|
|
@ -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 = ""
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package api
|
||||
|
||||
// Nothing to test
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package controller
|
||||
|
||||
// Nothing to test
|
|
@ -0,0 +1,3 @@
|
|||
package examples
|
||||
|
||||
// Nothing to test
|
|
@ -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})
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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"})
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
})
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package main
|
||||
|
||||
import "dev.sum7.eu/genofire/meetandeat/cmd"
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
|
@ -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{})
|
||||
}
|
|
@ -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{})
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package models
|
|
@ -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{})
|
||||
}
|
|
@ -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{})
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package runtime
|
||||
|
||||
// Nothing to test
|
|
@ -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))
|
||||
}
|
|
@ -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)])
|
||||
}
|
|
@ -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()
|
||||
|
||||
}
|
||||
}
|
|
@ -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
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,4 @@
|
|||
.p-navigation__logo {
|
||||
font-size: 1.5rem;
|
||||
line-height: 3rem;
|
||||
}
|
|
@ -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&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>
|
|
@ -0,0 +1,4 @@
|
|||
const app = new Vue({
|
||||
el: '#app',
|
||||
router,
|
||||
})
|
|
@ -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');
|
||||
}
|
|
@ -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' }
|
||||
]
|
||||
})
|
|
@ -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>`,
|
||||
}
|
|
@ -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>`,
|
||||
}
|
|
@ -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>`,
|
||||
}
|
|
@ -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>`,
|
||||
}
|
Loading…
Reference in New Issue