simplifying the repo, commands removed and proper libraries kept
This commit is contained in:
parent
cb0bc37e6b
commit
3bdd3e503e
48
Makefile
Normal file
48
Makefile
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
.DEFAULT_GOAL := help
|
||||||
|
SHELL := bash
|
||||||
|
.ONESHELL:
|
||||||
|
.SHELLFLAGS := -eu -o pipefail -c
|
||||||
|
.DELETE_ON_ERROR:
|
||||||
|
MAKEFLAGS += --warn-undefined-variables
|
||||||
|
MAKEFLAGS += --no-builtin-rules
|
||||||
|
|
||||||
|
ifeq ($(origin .RECIPEPREFIX), undefined)
|
||||||
|
$(error This Make does not support .RECIPEPREFIX. Please use GNU Make 4.0 or later)
|
||||||
|
endif
|
||||||
|
.RECIPEPREFIX = >
|
||||||
|
|
||||||
|
# Default - top level rule is what gets ran when you run just `make`
|
||||||
|
build: out/image-id
|
||||||
|
.PHONY: build
|
||||||
|
|
||||||
|
# Clean up the output directories; since all the sentinel files go under tmp, this will cause everything to get rebuilt
|
||||||
|
clean:
|
||||||
|
> rm -rf tmp
|
||||||
|
> rm -rf out
|
||||||
|
.PHONY: clean
|
||||||
|
|
||||||
|
# Tests - re-ran if any file under src has been changed since tmp/.tests-passed.sentinel was last touched
|
||||||
|
tmp/.tests-passed.sentinel: $(shell find src -type f)
|
||||||
|
> mkdir -p $(@D)
|
||||||
|
> node run test
|
||||||
|
> touch $@
|
||||||
|
|
||||||
|
# Webpack - re-built if the tests have been rebuilt (and so, by proxy, whenever the source files have changed)
|
||||||
|
tmp/.packed.sentinel: tmp/.tests-passed.sentinel
|
||||||
|
> mkdir -p $(@D)
|
||||||
|
> webpack ..
|
||||||
|
> touch $@
|
||||||
|
|
||||||
|
# Docker image - re-built if the webpack output has been rebuilt
|
||||||
|
out/image-id: tmp/.packed.sentinel
|
||||||
|
> mkdir -p $(@D)
|
||||||
|
> image_id="example.com/my-app:$$(pwgen -1)"
|
||||||
|
> docker build --tag="$${image_id}
|
||||||
|
> echo "$${image_id}" > out/image-id
|
||||||
|
|
||||||
|
help:
|
||||||
|
|
||||||
|
# @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
|
||||||
|
# | sed -n 's/^\(.*\): \(.*\)##\(.*\)/\1\3/p' \
|
||||||
|
# | column -t -s ' '
|
||||||
|
.PHONY: help
|
@ -1,22 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<script src="http://code.jquery.com/jquery-3.1.0.min.js" type="text/javascript"></script>
|
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
|
|
||||||
<!-- font awesome from BootstrapCDN -->
|
|
||||||
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
|
|
||||||
|
|
||||||
<link href="/public/app.css" rel="stylesheet">
|
|
||||||
</head>
|
|
||||||
<body class="home">
|
|
||||||
<div class="container">
|
|
||||||
<div class="login-page clearfix">
|
|
||||||
<div class="login-box auth0-box before">
|
|
||||||
<a id="qsLoginBtn" class="btn btn-primary btn-lg btn-block" href="/login">SignIn</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,34 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/codegangsta/negroni"
|
|
||||||
"github.com/jchenry/jchenry/internal/auth"
|
|
||||||
_http "github.com/jchenry/jchenry/internal/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
auth.Init()
|
|
||||||
StartServer()
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartServer() {
|
|
||||||
auth.PrintConfig()
|
|
||||||
s := _http.NewServer(negroni.New(), _http.NewJulienschmidtHTTPRouter()).
|
|
||||||
Static("/public/*filepath", http.Dir("public/")).
|
|
||||||
Service("", auth.Service(auth.FromEnv())).
|
|
||||||
Get("/", "", http.HandlerFunc(HomeHandler))
|
|
||||||
|
|
||||||
port := os.Getenv("PORT")
|
|
||||||
if port == "" {
|
|
||||||
port = "3000"
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Run(":" + port)
|
|
||||||
}
|
|
||||||
|
|
||||||
func HomeHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
_http.RenderTemplate(w, "home", nil)
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
body {
|
|
||||||
font-family: "proxima-nova", sans-serif;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 300%;
|
|
||||||
font-weight: 100;
|
|
||||||
}
|
|
||||||
input[type=checkbox],
|
|
||||||
input[type=radio] {
|
|
||||||
position: absolute;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
input[type=checkbox] + label,
|
|
||||||
input[type=radio] + label {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
input[type=checkbox] + label:before,
|
|
||||||
input[type=radio] + label:before {
|
|
||||||
content: "";
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: -0.2em;
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
border: 0.15em solid #0074d9;
|
|
||||||
border-radius: 0.2em;
|
|
||||||
margin-right: 0.3em;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
input[type=radio] + label:before {
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
input[type=radio]:checked + label:before,
|
|
||||||
input[type=checkbox]:checked + label:before {
|
|
||||||
background-color: #0074d9;
|
|
||||||
box-shadow: inset 0 0 0 0.15em white;
|
|
||||||
}
|
|
||||||
input[type=radio]:focus + label:before,
|
|
||||||
input[type=checkbox]:focus + label:before {
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
.btn {
|
|
||||||
font-size: 140%;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 1px;
|
|
||||||
border: 0;
|
|
||||||
background-color: #16214D;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.btn:hover {
|
|
||||||
background-color: #44C7F4;
|
|
||||||
}
|
|
||||||
.btn:focus {
|
|
||||||
outline: none !important;
|
|
||||||
}
|
|
||||||
.btn.btn-lg {
|
|
||||||
padding: 20px 30px;
|
|
||||||
}
|
|
||||||
.btn:disabled {
|
|
||||||
background-color: #333;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3 {
|
|
||||||
font-weight: 100;
|
|
||||||
}
|
|
||||||
#logo img {
|
|
||||||
width: 300px;
|
|
||||||
margin-bottom: 60px;
|
|
||||||
}
|
|
||||||
.home-description {
|
|
||||||
font-weight: 100;
|
|
||||||
margin: 100px 0;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
margin-top: 30px;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
font-size: 200%;
|
|
||||||
}
|
|
||||||
label {
|
|
||||||
font-size: 100%;
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
|
||||||
.btn-next {
|
|
||||||
margin-top: 30px;
|
|
||||||
}
|
|
||||||
.answer {
|
|
||||||
width: 70%;
|
|
||||||
margin: auto;
|
|
||||||
text-align: left;
|
|
||||||
padding-left: 10%;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.login-page .login-box {
|
|
||||||
padding: 100px 0;
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
AUTH_DOMAIN="https://dev-pb4s8m55.auth0.com/" AUTH_CLIENT_ID="ae1e02bTwXA35O3r3Xxk4kbRf31j5ge9" AUTH_CLIENT_SECRET="NFC5KYeM9GA2z0ptvzKPo9jmkQDRjx_WcsWyK0hzOJmr1CykS9cEmTcNh0-hKiMd" AUTH_CALLBACK_URL="http://localhost:3000/callback" ./auth-demo
|
|
@ -1,25 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<script src="http://code.jquery.com/jquery-3.1.0.min.js" type="text/javascript"></script>
|
|
||||||
|
|
||||||
<!-- font awesome from BootstrapCDN -->
|
|
||||||
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
|
|
||||||
|
|
||||||
<script src="/public/js.cookie.js"></script>
|
|
||||||
<script src="/public/user.js"> </script>
|
|
||||||
<link href="/public/app.css" rel="stylesheet">
|
|
||||||
</head>
|
|
||||||
<body class="home">
|
|
||||||
<div class="container">
|
|
||||||
<div class="login-page clearfix">
|
|
||||||
<div class="logged-in-box auth0-box logged-in">
|
|
||||||
<img class="avatar" src="{{.Picture}}"/>
|
|
||||||
<h2>Welcome {{.Nickname}}</h2>
|
|
||||||
<a id="qsLogoutBtn" class="btn btn-primary btn-lg btn-logout btn-block" href="/logout">Logout</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,78 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/rsc/rsc/google"
|
|
||||||
"github.com/rsc/rsc/imap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Pair struct {
|
|
||||||
Key string
|
|
||||||
Value uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type PairList []*Pair
|
|
||||||
|
|
||||||
func (p PairList) Len() int { return len(p) }
|
|
||||||
func (p PairList) Less(i, j int) bool { return p[i].Value < p[j].Value }
|
|
||||||
func (p PairList) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
|
|
||||||
acct := google.Acct("")
|
|
||||||
|
|
||||||
c, err := imap.NewClient(imap.TLS, "imap.gmail.com", acct.Email, acct.Password, "/")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
all := c.Inbox()
|
|
||||||
// all := c.Box("[Gmail]/All Mail")
|
|
||||||
|
|
||||||
if err := all.Check(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
msgs := all.Msgs()
|
|
||||||
|
|
||||||
counts := make(map[string]*Pair)
|
|
||||||
labels := make(map[string]struct{})
|
|
||||||
var pairs PairList = make([]*Pair, 0)
|
|
||||||
|
|
||||||
for _, m := range msgs {
|
|
||||||
ls := m.GmailLabels
|
|
||||||
for _, l := range ls {
|
|
||||||
labels[l] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
email := m.Hdr.From[0].Email
|
|
||||||
if _, ok := counts[email]; ok {
|
|
||||||
counts[email].Value = counts[email].Value + 1
|
|
||||||
} else {
|
|
||||||
p := Pair{email, 1}
|
|
||||||
pairs = append(pairs, &p)
|
|
||||||
counts[email] = &p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
keys := []string{}
|
|
||||||
|
|
||||||
for k := range counts {
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(pairs)
|
|
||||||
|
|
||||||
for _, e := range pairs {
|
|
||||||
fmt.Printf("%d %s\t\t\thttps://mail.google.com/mail/u/0/#search/in%%3Ainbox+from%%3A(%s)\n", e.Value, e.Key, e.Key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// for k, _ := range labels {
|
|
||||||
// fmt.Printf("%s, ", k)
|
|
||||||
// }
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/PuloV/ics-golang"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
|
|
||||||
// calendarFile = flag.String("f", os.Env, "the calendar to convert")
|
|
||||||
help := flag.Bool("help", false, "this help.")
|
|
||||||
flag.Parse()
|
|
||||||
if *help {
|
|
||||||
flag.Usage()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
parser := ics.New()
|
|
||||||
parserChan := parser.GetInputChan()
|
|
||||||
outputChan := parser.GetOutputChan()
|
|
||||||
go func() {
|
|
||||||
nowYear := time.Now().Year()
|
|
||||||
for event := range outputChan {
|
|
||||||
if event.GetStart().Year() == nowYear {
|
|
||||||
printEvent(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
parserChan <- "https://calendar.google.com/calendar/ical/colin%40jchenry.me/private-ff5ffa18eb856032d166c7f410fe33c0/basic.ics"
|
|
||||||
|
|
||||||
parser.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func printEvent(evt *ics.Event) {
|
|
||||||
fmt.Printf("%s - %s : %s (%s)\n", fmtTime(evt.GetStart()), fmtTime(evt.GetEnd()), evt.GetSummary(), evt.GetLocation())
|
|
||||||
}
|
|
||||||
|
|
||||||
func fmtTime(t time.Time) string {
|
|
||||||
loc, err := time.LoadLocation("America/Los_Angeles")
|
|
||||||
if err != nil {
|
|
||||||
panic("bad timezone")
|
|
||||||
}
|
|
||||||
|
|
||||||
return t.In(loc).Format("Jan 02\t2006 15:04 MST")
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jchenry/jchenry/pkg/arvelie"
|
|
||||||
"github.com/jchenry/jchenry/pkg/neralie"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := run(); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func run() (err error) {
|
|
||||||
PS1 := "[%]: "
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
|
||||||
|
|
||||||
for {
|
|
||||||
fmt.Print(PS1)
|
|
||||||
if input, err := reader.ReadString('\n'); err == nil {
|
|
||||||
if err = execute(input); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, err.Error())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Fprintln(os.Stderr, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func execute(input string) error {
|
|
||||||
input = strings.TrimSuffix(input, "\n")
|
|
||||||
args := strings.Split(input, " ")
|
|
||||||
|
|
||||||
switch args[0] {
|
|
||||||
case "cd":
|
|
||||||
if len(args) < 2 {
|
|
||||||
return errors.New("path required")
|
|
||||||
}
|
|
||||||
return os.Chdir(args[1])
|
|
||||||
case "now":
|
|
||||||
t := time.Now()
|
|
||||||
fmt.Printf("%s %s\n", arvelie.FromDate(t), neralie.FromTime(t))
|
|
||||||
return nil
|
|
||||||
case "exit":
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(args[0], args[1:]...)
|
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jchenry/jchenry/pkg/neralie"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
a := neralie.FromTime(time.Now())
|
|
||||||
fmt.Println(a)
|
|
||||||
}
|
|
BIN
cmd/now/now
BIN
cmd/now/now
Binary file not shown.
@ -1,22 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<script src="http://code.jquery.com/jquery-3.1.0.min.js" type="text/javascript"></script>
|
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
|
|
||||||
<!-- font awesome from BootstrapCDN -->
|
|
||||||
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
|
|
||||||
|
|
||||||
<link href="/public/app.css" rel="stylesheet">
|
|
||||||
</head>
|
|
||||||
<body class="home">
|
|
||||||
<div class="container">
|
|
||||||
<div class="login-page clearfix">
|
|
||||||
<div class="login-box auth0-box before">
|
|
||||||
<a id="qsLoginBtn" class="btn btn-primary btn-lg btn-block" href="/login">SignIn</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,39 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/codegangsta/negroni"
|
|
||||||
"github.com/jchenry/jchenry/internal/auth"
|
|
||||||
_http "github.com/jchenry/jchenry/internal/http"
|
|
||||||
"github.com/jchenry/jchenry/internal/payments"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
auth.Init()
|
|
||||||
StartServer()
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartServer() {
|
|
||||||
auth.PrintConfig()
|
|
||||||
payments.PrintConfig()
|
|
||||||
|
|
||||||
auth_service := auth.Service(auth.FromEnv())
|
|
||||||
s := _http.NewServer(negroni.New(), _http.NewJulienschmidtHTTPRouter()).
|
|
||||||
Static("/public/*filepath", http.Dir("public/")).
|
|
||||||
Service("", auth_service).
|
|
||||||
Service("", payments.Service(payments.FromEnv(), &auth_service)).
|
|
||||||
Get("/", "", http.HandlerFunc(HomeHandler))
|
|
||||||
|
|
||||||
port := os.Getenv("PORT")
|
|
||||||
if port == "" {
|
|
||||||
port = "3000"
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Run(":" + port)
|
|
||||||
}
|
|
||||||
|
|
||||||
func HomeHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
_http.RenderTemplate(w, "home", nil)
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
body {
|
|
||||||
font-family: "proxima-nova", sans-serif;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 300%;
|
|
||||||
font-weight: 100;
|
|
||||||
}
|
|
||||||
input[type=checkbox],
|
|
||||||
input[type=radio] {
|
|
||||||
position: absolute;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
input[type=checkbox] + label,
|
|
||||||
input[type=radio] + label {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
input[type=checkbox] + label:before,
|
|
||||||
input[type=radio] + label:before {
|
|
||||||
content: "";
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: -0.2em;
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
border: 0.15em solid #0074d9;
|
|
||||||
border-radius: 0.2em;
|
|
||||||
margin-right: 0.3em;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
input[type=radio] + label:before {
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
input[type=radio]:checked + label:before,
|
|
||||||
input[type=checkbox]:checked + label:before {
|
|
||||||
background-color: #0074d9;
|
|
||||||
box-shadow: inset 0 0 0 0.15em white;
|
|
||||||
}
|
|
||||||
input[type=radio]:focus + label:before,
|
|
||||||
input[type=checkbox]:focus + label:before {
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
.btn {
|
|
||||||
font-size: 140%;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 1px;
|
|
||||||
border: 0;
|
|
||||||
background-color: #16214D;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.btn:hover {
|
|
||||||
background-color: #44C7F4;
|
|
||||||
}
|
|
||||||
.btn:focus {
|
|
||||||
outline: none !important;
|
|
||||||
}
|
|
||||||
.btn.btn-lg {
|
|
||||||
padding: 20px 30px;
|
|
||||||
}
|
|
||||||
.btn:disabled {
|
|
||||||
background-color: #333;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3 {
|
|
||||||
font-weight: 100;
|
|
||||||
}
|
|
||||||
#logo img {
|
|
||||||
width: 300px;
|
|
||||||
margin-bottom: 60px;
|
|
||||||
}
|
|
||||||
.home-description {
|
|
||||||
font-weight: 100;
|
|
||||||
margin: 100px 0;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
margin-top: 30px;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
font-size: 200%;
|
|
||||||
}
|
|
||||||
label {
|
|
||||||
font-size: 100%;
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
|
||||||
.btn-next {
|
|
||||||
margin-top: 30px;
|
|
||||||
}
|
|
||||||
.answer {
|
|
||||||
width: 70%;
|
|
||||||
margin: auto;
|
|
||||||
text-align: left;
|
|
||||||
padding-left: 10%;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.login-page .login-box {
|
|
||||||
padding: 100px 0;
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
AUTH_DOMAIN="https://dev-pb4s8m55.auth0.com/" \
|
|
||||||
AUTH_CLIENT_ID="ae1e02bTwXA35O3r3Xxk4kbRf31j5ge9" \
|
|
||||||
AUTH_CLIENT_SECRET="NFC5KYeM9GA2z0ptvzKPo9jmkQDRjx_WcsWyK0hzOJmr1CykS9cEmTcNh0-hKiMd" \
|
|
||||||
AUTH_CALLBACK_URL="http://localhost:3000/callback" \
|
|
||||||
AUTH_MGMT_CLIENT_ID="0SpgXDo7HleFZ6NH9ds2t2vkntEzgYqy" \
|
|
||||||
AUTH_MGMT_CLIENT_SECRET="DhOE1JqO7A2uosadjuyCluK5P3NlxOf4V9mPkEDy4gDO_lYnMffzYfVpgcINvzfr" \
|
|
||||||
STRIPE_KEY="sk_test_3yIcJl5ev3WfFZ4HNbTMqWv800B26e0c4V" \
|
|
||||||
STRIPE_PRODUCT_ID="prod_FtzugcD89mNVhp" \
|
|
||||||
./sub-demo
|
|
@ -1,123 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<script src="http://code.jquery.com/jquery-3.1.0.min.js" type="text/javascript"></script>
|
|
||||||
|
|
||||||
<!-- font awesome from BootstrapCDN -->
|
|
||||||
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
|
|
||||||
|
|
||||||
<script src="/public/js.cookie.js"></script>
|
|
||||||
<link href="/public/app.css" rel="stylesheet">
|
|
||||||
<script src="https://js.stripe.com/v3/"></script>
|
|
||||||
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="home">
|
|
||||||
<div class="container">
|
|
||||||
{{.Product.Name}}
|
|
||||||
<form method="POST" id="payment-form">
|
|
||||||
<div class="form-group">
|
|
||||||
{{range .Plans}}
|
|
||||||
<div class="form-check">
|
|
||||||
<input type="radio" name="plan" class="form-control" id="{{.ID}}" value="{{.ID}}">
|
|
||||||
<label class="form-check-label" for="{{.ID}}">
|
|
||||||
{{.Nickname}}({{.AmountDecimal}})
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<label for="card-element">
|
|
||||||
Credit or debit card
|
|
||||||
</label>
|
|
||||||
<div id="card-element">
|
|
||||||
<!-- A Stripe Element will be inserted here. -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Used to display form errors. -->
|
|
||||||
<div id="card-errors" role="alert"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button>Submit Payment</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
// Create a Stripe client.
|
|
||||||
var stripe = Stripe('pk_test_ghSfaAtzKpBNxyrRlgGPuVBu00v5d1ptjL');
|
|
||||||
|
|
||||||
// Create an instance of Elements.
|
|
||||||
var elements = stripe.elements();
|
|
||||||
|
|
||||||
// Custom styling can be passed to options when creating an Element.
|
|
||||||
// (Note that this demo uses a wider set of styles than the guide below.)
|
|
||||||
var style = {
|
|
||||||
base: {
|
|
||||||
color: '#32325d',
|
|
||||||
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
|
|
||||||
fontSmoothing: 'antialiased',
|
|
||||||
fontSize: '16px',
|
|
||||||
'::placeholder': {
|
|
||||||
color: '#aab7c4'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
invalid: {
|
|
||||||
color: '#fa755a',
|
|
||||||
iconColor: '#fa755a'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create an instance of the card Element.
|
|
||||||
var card = elements.create('card', { style: style });
|
|
||||||
|
|
||||||
// Add an instance of the card Element into the `card-element` <div>.
|
|
||||||
card.mount('#card-element');
|
|
||||||
|
|
||||||
// Handle real-time validation errors from the card Element.
|
|
||||||
card.addEventListener('change', function (event) {
|
|
||||||
var displayError = document.getElementById('card-errors');
|
|
||||||
if (event.error) {
|
|
||||||
displayError.textContent = event.error.message;
|
|
||||||
} else {
|
|
||||||
displayError.textContent = '';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle form submission.
|
|
||||||
var form = document.getElementById('payment-form');
|
|
||||||
form.addEventListener('submit', function (event) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
stripe.createToken(card).then(function (result) {
|
|
||||||
if (result.error) {
|
|
||||||
// Inform the user if there was an error.
|
|
||||||
var errorElement = document.getElementById('card-errors');
|
|
||||||
errorElement.textContent = result.error.message;
|
|
||||||
} else {
|
|
||||||
// Send the token to your server.
|
|
||||||
stripeTokenHandler(result.token);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Submit the form with the token ID.
|
|
||||||
function stripeTokenHandler(token) {
|
|
||||||
// Insert the token ID into the form so it gets submitted to the server
|
|
||||||
var form = document.getElementById('payment-form');
|
|
||||||
var hiddenInput = document.createElement('input');
|
|
||||||
hiddenInput.setAttribute('type', 'hidden');
|
|
||||||
hiddenInput.setAttribute('name', 'stripeToken');
|
|
||||||
hiddenInput.setAttribute('value', token.id);
|
|
||||||
form.appendChild(hiddenInput);
|
|
||||||
|
|
||||||
// Submit the form
|
|
||||||
form.submit();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,36 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<script src="http://code.jquery.com/jquery-3.1.0.min.js" type="text/javascript"></script>
|
|
||||||
|
|
||||||
<!-- font awesome from BootstrapCDN -->
|
|
||||||
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
|
|
||||||
|
|
||||||
<script src="/public/js.cookie.js"></script>
|
|
||||||
<script src="/public/user.js"> </script>
|
|
||||||
<link href="/public/app.css" rel="stylesheet">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="home">
|
|
||||||
<div class="container">
|
|
||||||
<div class="login-page clearfix">
|
|
||||||
<div class="logged-in-box auth0-box logged-in">
|
|
||||||
<img class="avatar" src="{{.Picture}}" />
|
|
||||||
<h2>Welcome {{.Nickname}}</h2>
|
|
||||||
<a id="qsLogoutBtn" class="btn btn-primary btn-lg btn-logout btn-block" href="/logout">Logout</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
// $(document).ready(function () {
|
|
||||||
// $('.btn-logout').click(function (e) {
|
|
||||||
// Cookies.remove('auth-session');
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,13 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jchenry/jchenry/pkg/arvelie"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
a := arvelie.FromDate(time.Now())
|
|
||||||
fmt.Println(a)
|
|
||||||
}
|
|
BIN
cmd/today/today
BIN
cmd/today/today
Binary file not shown.
@ -1,70 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/jchenry/jchenry/db"
|
|
||||||
"rsc.io/dbstore"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DBFunc func(db *sql.DB)
|
|
||||||
|
|
||||||
type DBActor struct {
|
|
||||||
DB *sql.DB
|
|
||||||
ActionChan chan DBFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *DBActor) Run(ctx context.Context) error {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case f := <-a.ActionChan:
|
|
||||||
f(a.DB)
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DBStoreInsert(store *dbstore.Storage, e interface{}) db.Func {
|
|
||||||
return func(db *sql.DB) {
|
|
||||||
err := store.Insert(db, e)
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DBStoreDelete(store *dbstore.Storage, e interface{}) db.Func {
|
|
||||||
return func(db *sql.DB) {
|
|
||||||
store.Delete(db, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DBStoreSelect(store *dbstore.Storage,
|
|
||||||
err chan error,
|
|
||||||
results chan interface{},
|
|
||||||
ent interface{},
|
|
||||||
query string,
|
|
||||||
args ...interface{}) db.Func {
|
|
||||||
return func(db *sql.DB) {
|
|
||||||
if e := store.Select(db, ent, query, args...); e != nil {
|
|
||||||
err <- e
|
|
||||||
} else {
|
|
||||||
results <- ent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DBStoreRead(store *dbstore.Storage,
|
|
||||||
err chan error,
|
|
||||||
results chan interface{},
|
|
||||||
ent interface{},
|
|
||||||
columns ...string) db.Func {
|
|
||||||
return func(db *sql.DB) {
|
|
||||||
if e := store.Read(db, ent, columns...); e != nil {
|
|
||||||
err <- e
|
|
||||||
} else {
|
|
||||||
results <- ent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
"text/template"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *server) handleEcho() http.HandlerFunc {
|
|
||||||
var (
|
|
||||||
init sync.Once
|
|
||||||
tmpl *template.Template
|
|
||||||
tplerr error
|
|
||||||
)
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
init.Do(func() {
|
|
||||||
tmpl, tplerr = template.ParseFiles("echoform.tmpl")
|
|
||||||
})
|
|
||||||
if tplerr != nil {
|
|
||||||
http.Error(w, tplerr.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch r.Method {
|
|
||||||
case http.MethodPost:
|
|
||||||
r.ParseForm()
|
|
||||||
s.echoMessage = r.Form.Get("msg")
|
|
||||||
http.Redirect(w, r, "/echo", 301)
|
|
||||||
return
|
|
||||||
case http.MethodGet:
|
|
||||||
if err := tmpl.Execute(w, map[string]string{"Message": s.echoMessage}); err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
<html>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
{{if (eq .Message "")}}
|
|
||||||
<form action="/echo" method="post">
|
|
||||||
<input type="text" id="msg" name="msg">
|
|
||||||
<input type="submit" value="Submit">
|
|
||||||
</form>
|
|
||||||
{{else}}
|
|
||||||
|
|
||||||
{{.Message}}
|
|
||||||
|
|
||||||
{{end}}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Binary file not shown.
@ -1,96 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/jchenry/jchenry/db"
|
|
||||||
"rsc.io/dbstore"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *server) handleFortune() http.HandlerFunc {
|
|
||||||
var (
|
|
||||||
init sync.Once
|
|
||||||
dba *db.Actor
|
|
||||||
storage *dbstore.Storage
|
|
||||||
tmpl *template.Template
|
|
||||||
tplerr error
|
|
||||||
)
|
|
||||||
|
|
||||||
type fortuneWrapper struct {
|
|
||||||
Fortune string
|
|
||||||
}
|
|
||||||
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
init.Do(func() {
|
|
||||||
ctx, _ := context.WithCancel(context.Background())
|
|
||||||
dba = &db.Actor{
|
|
||||||
DB: s.db,
|
|
||||||
ActionChan: make(chan db.Func),
|
|
||||||
}
|
|
||||||
go dba.Run(ctx)
|
|
||||||
storage = new(dbstore.Storage)
|
|
||||||
storage.Register(&fortuneWrapper{})
|
|
||||||
err := storage.CreateTables(dba.DB)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
tmpl, tplerr = template.ParseFiles("fortunesupload.tmpl")
|
|
||||||
})
|
|
||||||
|
|
||||||
if tplerr != nil {
|
|
||||||
http.Error(w, tplerr.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch r.Method {
|
|
||||||
case http.MethodPost:
|
|
||||||
r.ParseMultipartForm(10 << 20)
|
|
||||||
file, _, err := r.FormFile("fortunes")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error Retrieving the File")
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(file)
|
|
||||||
var fortune string
|
|
||||||
for scanner.Scan() {
|
|
||||||
switch scanner.Text() {
|
|
||||||
case "%":
|
|
||||||
dba.ActionChan <- DBStoreInsert(storage, &fortuneWrapper{Fortune: fortune})
|
|
||||||
default:
|
|
||||||
fortune = scanner.Text()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
case http.MethodGet:
|
|
||||||
f := fortuneWrapper{}
|
|
||||||
results := make(chan interface{})
|
|
||||||
err := make(chan error)
|
|
||||||
|
|
||||||
dba.ActionChan <- DBStoreSelect(storage, err, results, &f, "ORDER BY RANDOM() LIMIT 1", "*")
|
|
||||||
select {
|
|
||||||
case r := <-results:
|
|
||||||
if err := tmpl.Execute(w, map[string]string{"Message": r.(*fortuneWrapper).Fortune}); err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
case e := <-err:
|
|
||||||
http.Error(w, e.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
<html>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
{{if (eq .Message "")}}
|
|
||||||
<form
|
|
||||||
enctype="multipart/form-data"
|
|
||||||
method="post">
|
|
||||||
<input type="file" name="fortunes" />
|
|
||||||
<input type="submit" value="upload" />
|
|
||||||
</form>
|
|
||||||
{{else}}
|
|
||||||
{{.Message}}
|
|
||||||
{{end}}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,22 +0,0 @@
|
|||||||
module github.com/jchenry/jchenry/cmd/web-tinkertoy
|
|
||||||
|
|
||||||
go 1.14
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/codegangsta/negroni v1.0.0 // indirect
|
|
||||||
github.com/coreos/go-oidc v2.1.0+incompatible // indirect
|
|
||||||
github.com/gorilla/sessions v1.2.0 // indirect
|
|
||||||
github.com/jchenry/jchenry v0.0.0-00010101000000-000000000000
|
|
||||||
github.com/julienschmidt/httprouter v1.2.0 // indirect
|
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
|
||||||
github.com/stretchr/testify v1.4.0 // indirect
|
|
||||||
github.com/stripe/stripe-go v63.4.0+incompatible // indirect
|
|
||||||
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad // indirect
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 // indirect
|
|
||||||
gopkg.in/auth0.v1 v1.2.7 // indirect
|
|
||||||
gopkg.in/square/go-jose.v2 v2.3.1 // indirect
|
|
||||||
rsc.io/dbstore v0.1.1
|
|
||||||
)
|
|
||||||
|
|
||||||
replace github.com/jchenry/jchenry => ../../
|
|
@ -1,42 +0,0 @@
|
|||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
github.com/PuerkitoBio/rehttp v0.0.0-20180310210549-11cf6ea5d3e9/go.mod h1:ItsOiHl4XeMOV3rzbZqQRjLc3QQxbE6391/9iNG7rE8=
|
|
||||||
github.com/PuloV/ics-golang v0.0.0-20190808201353-a3394d3bcade/go.mod h1:f1P3hjG+t54/IrnXMnnw+gRmFCDR/ryj9xSQ7MPMkQw=
|
|
||||||
github.com/channelmeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61/go.mod h1:Rp8e0DCtEKwXFOC6JPJQVTz8tuGoGvw6Xfexggh/ed0=
|
|
||||||
github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0=
|
|
||||||
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
|
||||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
|
||||||
github.com/jchenry/jchenry v0.0.0-20191107034549-4697006d1099 h1:qUy+BhiLM5LzN0cOGtqvuC2v7FNfmQFg1cjZbNcMnBY=
|
|
||||||
github.com/jchenry/jchenry v0.0.0-20191107034549-4697006d1099/go.mod h1:5ywsKLPV6YOTZ7oNNmQo7EQRDKhlGCD311r/ZRZgHDM=
|
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
|
||||||
github.com/mattn/go-sqlite3 v1.13.0 h1:LnJI81JidiW9r7pS/hXe6cFeO5EXNq7KbfvoJLRI69c=
|
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/stripe/stripe-go v63.4.0+incompatible/go.mod h1:A1dQZmO/QypXmsL0T8axYZkSN/uA/T/A64pfKdBAMiY=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
gopkg.in/auth0.v1 v1.2.7/go.mod h1:1FRtMXwYDgygZcO7Of7kj/I4mf9UjHGhMHUOqNT0d0M=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
rsc.io/dbstore v0.1.1 h1:LI4gBJUwbejn0wHJWe0KTwgCM33zUVP3BsNz5y2fkEE=
|
|
||||||
rsc.io/dbstore v0.1.1/go.mod h1:zI7k1PCSLg9r/T2rBM4E/SctbGmqdtt3kjQSemVh1Rs=
|
|
||||||
rsc.io/sqlite v0.5.0/go.mod h1:fqHuveM9iIqMzjD0WiZIvKYMty/WqTo2bxE9+zC54WE=
|
|
@ -1,34 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := run(os.Args, os.Stdout); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func run(args []string, stdout io.Writer) error {
|
|
||||||
s := server{
|
|
||||||
router: http.NewServeMux(),
|
|
||||||
}
|
|
||||||
|
|
||||||
s.routes()
|
|
||||||
|
|
||||||
if db, err := sql.Open("sqlite3", "foo.db"); err == nil {
|
|
||||||
s.db = db
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return http.ListenAndServe(":8080", s.router)
|
|
||||||
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
type server struct {
|
|
||||||
db *sql.DB
|
|
||||||
router *http.ServeMux
|
|
||||||
echoMessage string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) routes() {
|
|
||||||
s.router.HandleFunc("/time", s.handleTime())
|
|
||||||
s.router.HandleFunc("/echo", s.handleEcho())
|
|
||||||
s.router.HandleFunc("/fortune", s.handleFortune())
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jchenry/jchenry/arvelie"
|
|
||||||
"github.com/jchenry/jchenry/neralie"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *server) handleTime() http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
t := time.Now()
|
|
||||||
w.WriteHeader(200)
|
|
||||||
io.WriteString(w, t.String())
|
|
||||||
io.WriteString(w, fmt.Sprintf("\n%s %s",
|
|
||||||
arvelie.FromDate(t),
|
|
||||||
neralie.FromTime(t)))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func edit(pageName string, w http.ResponseWriter, r *http.Request) (err error) {
|
|
||||||
if body, err := getFile(pageName, os.O_RDWR|os.O_CREATE); err == nil {
|
|
||||||
return render(pageName, "edit", body, w)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func auth(fn http.HandlerFunc) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
user, pass, _ := r.BasicAuth()
|
|
||||||
if !(user == os.Getenv("WIKI_USERNAME") && pass == os.Getenv("WIKI_PASSWORD")) {
|
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="wiki"`)
|
|
||||||
http.Error(w, "Unauthorized.", 401)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fn(w, r)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
type actionFunc func(s string, w http.ResponseWriter, r *http.Request) error
|
|
||||||
|
|
||||||
var pageDir *string
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
p, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
pageDir = flag.String("pageDir", path.Join(p, "pages"), "the directory in which pages exist")
|
|
||||||
httpAddr := flag.String("http", "127.0.0.1:8080", " HTTP service address")
|
|
||||||
help := flag.Bool("help", false, "this help.")
|
|
||||||
flag.Parse()
|
|
||||||
if *help {
|
|
||||||
flag.Usage()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for path, action := range map[string]actionFunc{"/wiki/": view, "/edit/": edit, "/save/": save, "/search/": search} {
|
|
||||||
register(path, action)
|
|
||||||
}
|
|
||||||
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
|
||||||
http.Handle("/", auth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { view("WelcomeVisitors", w, r) })))
|
|
||||||
log.Printf("using log/pass: %s/%s", os.Getenv("WIKI_USERNAME"), os.Getenv("WIKI_PASSWORD"))
|
|
||||||
log.Printf("wiki has started listening at %s", *httpAddr)
|
|
||||||
log.Fatal(http.ListenAndServe(*httpAddr, nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func register(path string, action actionFunc) {
|
|
||||||
http.Handle(path, http.StripPrefix(path, auth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.URL.Path != "" {
|
|
||||||
if err := action(r.URL.Path, w, r); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))))
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getFile(pageName string, flags int) (file []byte, err error) {
|
|
||||||
if f, err := os.OpenFile(path.Join(*pageDir, pageName), flags, 0755); err == nil {
|
|
||||||
return ioutil.ReadAll(f)
|
|
||||||
}
|
|
||||||
return file, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveFile(pageName string, contents []byte) error {
|
|
||||||
return ioutil.WriteFile(path.Join(*pageDir, pageName), contents, 0700)
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"text/template"
|
|
||||||
)
|
|
||||||
|
|
||||||
func render(p string, m string, body []byte, w http.ResponseWriter) (err error) {
|
|
||||||
if tmpl, err := template.ParseFiles("page.tmpl.html"); err == nil {
|
|
||||||
return tmpl.Execute(w, struct {
|
|
||||||
Mode string
|
|
||||||
Body string
|
|
||||||
Page string
|
|
||||||
}{m, string(body), p})
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func save(pageName string, w http.ResponseWriter, r *http.Request) (err error) {
|
|
||||||
r.ParseForm()
|
|
||||||
if err = saveFile(pageName, []byte(r.Form.Get("Text"))); err == nil {
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/wiki/%s", pageName), http.StatusTemporaryRedirect)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
)
|
|
||||||
|
|
||||||
const resultFmt = "<a href=/wiki/%s>%s</a> . . . . . . %s<br>\n"
|
|
||||||
|
|
||||||
func search(keyword string, w http.ResponseWriter, r *http.Request) (err error) {
|
|
||||||
var results string
|
|
||||||
if files, err := ioutil.ReadDir(*pageDir); err == nil {
|
|
||||||
re := regexp.MustCompile(keyword)
|
|
||||||
for _, f := range files {
|
|
||||||
if f.Name() == keyword {
|
|
||||||
results += fmt.Sprintf(resultFmt, f.Name(), f.Name(), f.Name())
|
|
||||||
}
|
|
||||||
if body, err := getFile(f.Name(), os.O_RDWR); err == nil {
|
|
||||||
for _, occur := range re.FindSubmatch(body) {
|
|
||||||
results += fmt.Sprintf(resultFmt, f.Name(), f.Name(), occur)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
render("search", "view", []byte(results), w)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/russross/blackfriday/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var patterns [5]*regexp.Regexp
|
|
||||||
var renderers [5]func([]byte) []byte
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// /*autoLinkRegexp*/ patterns[0], renderers[0] = regexp.MustCompile("[A-Z][a-z0-9]+([A-Z][a-z0-9]+)+"), func(s []byte) []byte { return []byte(fmt.Sprintf(`<a href="/wiki/%s/">%s</a>`, string(s), string(s))) }
|
|
||||||
/*BracketedAutoLinkRegexp*/
|
|
||||||
patterns[0], renderers[0] = regexp.MustCompile("\\[\\[[A-Za-z0-9 ]+([A-Za-z0-9 ]+)+\\]\\]"), func(s []byte) []byte { return []byte(fmt.Sprintf(`<a href="/wiki/%s/">%s</a>`, string(s), string(s))) }
|
|
||||||
|
|
||||||
/*searchRegexp*/
|
|
||||||
patterns[1], renderers[1] = regexp.MustCompile("\\[Search\\]"), func(s []byte) []byte {
|
|
||||||
return []byte(`<form id="search_form" action="/search" onsubmit="searchHelper()"><input type="text" size="40" name="search" value=""><input type="submit" value="Search"></form>`)
|
|
||||||
}
|
|
||||||
/*youTubeLinkRegexp*/ patterns[2], renderers[2] = regexp.MustCompile("https://(www.)?youtube.com/watch\\?v=([-\\w]+)"), func(s []byte) []byte {
|
|
||||||
return []byte(fmt.Sprintf(`<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/%s?rel=0" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>`, strings.Split(string(s), "=")[1]))
|
|
||||||
}
|
|
||||||
/*isbnLinkRegexp*/ patterns[3], renderers[3] = regexp.MustCompile("ISBN:*([0-9]{10,})"), func(s []byte) []byte {
|
|
||||||
return []byte(fmt.Sprintf(`<a href="http://www.amazon.com/exec/obidos/ISBN=%s" rel="nofollow">ISBN %s</a>`, bytes.Replace(bytes.Split(s, []byte(":"))[1], []byte("-"), []byte(""), -1), bytes.Split(s, []byte(":"))[1]))
|
|
||||||
}
|
|
||||||
/*alltextRegexp*/ patterns[4], renderers[4] = regexp.MustCompile(".*"), func(s []byte) []byte {
|
|
||||||
return blackfriday.Run(s, blackfriday.WithExtensions(blackfriday.CommonExtensions))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func view(pageName string, w http.ResponseWriter, r *http.Request) (err error) {
|
|
||||||
var body []byte
|
|
||||||
if body, err = getFile(pageName, os.O_RDWR); os.IsNotExist(err) {
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/edit/%s", pageName), http.StatusTemporaryRedirect) // no page? redirect to edit/create it.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for i := range renderers {
|
|
||||||
body = patterns[i].ReplaceAllFunc(body, renderers[i])
|
|
||||||
}
|
|
||||||
return render(pageName, "view", body, w)
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package db
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
@ -1,4 +1,5 @@
|
|||||||
package db
|
package database
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
Example usage:
|
Example usage:
|
6
encoding/coder.go
Normal file
6
encoding/coder.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package encoding
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
type Encoder func(io.Writer, interface{}) error
|
||||||
|
type Decoder func(io.Reader, interface{}) error
|
23
encoding/json/json.go
Normal file
23
encoding/json/json.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Encoder(w io.Writer, e interface{}) error {
|
||||||
|
return json.NewEncoder(w).Encode(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Decoder(r io.Reader, e interface{}) error {
|
||||||
|
return json.NewDecoder(r).Decode(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// func Decoder(get func() interface{}) func(io.Reader) (interface{}, error) {
|
||||||
|
// //TODO I dont like the get() function, find a better way of dealing with this
|
||||||
|
// return func(r io.Reader) (interface{}, error) {
|
||||||
|
// e := get()
|
||||||
|
// err := json.NewDecoder(r).Decode(e)
|
||||||
|
// return e, err
|
||||||
|
// }
|
||||||
|
// }
|
24
encoding/xml/xml.go
Normal file
24
encoding/xml/xml.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package xml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Encoder(w io.Writer, e interface{}) error {
|
||||||
|
return xml.NewEncoder(w).Encode(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Decoder(r io.Reader, e interface{}) error {
|
||||||
|
return xml.NewDecoder(r).Decode(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// func Decoder(get func() interface{}) func(io.Reader) (interface{}, error) {
|
||||||
|
// //TODO I dont like the get() function, find a better way of dealing with this
|
||||||
|
// return func(r io.Reader) (interface{}, error) {
|
||||||
|
// e := get()
|
||||||
|
// err := xml.NewDecoder(r).Decode(e)
|
||||||
|
// return e, err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
7
go.mod
7
go.mod
@ -1,21 +1,22 @@
|
|||||||
module github.com/jchenry/jchenry
|
module github.com/jchenry/x
|
||||||
|
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
fyne.io/fyne v1.3.0
|
||||||
github.com/PuloV/ics-golang v0.0.0-20190808201353-a3394d3bcade
|
github.com/PuloV/ics-golang v0.0.0-20190808201353-a3394d3bcade
|
||||||
github.com/channelmeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61 // indirect
|
github.com/channelmeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61 // indirect
|
||||||
github.com/codegangsta/negroni v1.0.0
|
github.com/codegangsta/negroni v1.0.0
|
||||||
github.com/coreos/go-oidc v2.1.0+incompatible
|
github.com/coreos/go-oidc v2.1.0+incompatible
|
||||||
github.com/gorilla/sessions v1.2.0
|
github.com/gorilla/sessions v1.2.0
|
||||||
|
github.com/jchenry/jchenry v0.0.0-20200615172632-cb0bc37e6b16
|
||||||
github.com/julienschmidt/httprouter v1.2.0
|
github.com/julienschmidt/httprouter v1.2.0
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||||
github.com/rsc/rsc v0.0.0-20180427141835-fc6202590229
|
github.com/rsc/rsc v0.0.0-20180427141835-fc6202590229
|
||||||
github.com/russross/blackfriday/v2 v2.0.1
|
github.com/russross/blackfriday/v2 v2.0.1
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||||
github.com/stretchr/testify v1.4.0 // indirect
|
|
||||||
github.com/stripe/stripe-go v63.4.0+incompatible
|
github.com/stripe/stripe-go v63.4.0+incompatible
|
||||||
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad // indirect
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||||
gopkg.in/auth0.v1 v1.2.7
|
gopkg.in/auth0.v1 v1.2.7
|
||||||
gopkg.in/square/go-jose.v2 v2.3.1 // indirect
|
gopkg.in/square/go-jose.v2 v2.3.1 // indirect
|
||||||
|
66
go.sum
66
go.sum
@ -1,8 +1,12 @@
|
|||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
fyne.io/fyne v1.3.0 h1:FLlgX/JkD3Chal7tEhRL7fOONVAjQJM/yrVNA+cK/dc=
|
||||||
|
fyne.io/fyne v1.3.0/go.mod h1:AcBUeR8hetITnnfaLvuVqioWM/lT18WPeMVAobhMbg8=
|
||||||
|
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
|
||||||
github.com/PuerkitoBio/rehttp v0.0.0-20180310210549-11cf6ea5d3e9 h1:VE0eMvNSQI72dADsq4gm5KpNPmt97WgqneTfaS5MWrs=
|
github.com/PuerkitoBio/rehttp v0.0.0-20180310210549-11cf6ea5d3e9 h1:VE0eMvNSQI72dADsq4gm5KpNPmt97WgqneTfaS5MWrs=
|
||||||
github.com/PuerkitoBio/rehttp v0.0.0-20180310210549-11cf6ea5d3e9/go.mod h1:ItsOiHl4XeMOV3rzbZqQRjLc3QQxbE6391/9iNG7rE8=
|
github.com/PuerkitoBio/rehttp v0.0.0-20180310210549-11cf6ea5d3e9/go.mod h1:ItsOiHl4XeMOV3rzbZqQRjLc3QQxbE6391/9iNG7rE8=
|
||||||
github.com/PuloV/ics-golang v0.0.0-20190808201353-a3394d3bcade h1:odEkSCl2gLWPtvraEdCyBZbeYyMMTysWPLMurnB8sUY=
|
github.com/PuloV/ics-golang v0.0.0-20190808201353-a3394d3bcade h1:odEkSCl2gLWPtvraEdCyBZbeYyMMTysWPLMurnB8sUY=
|
||||||
github.com/PuloV/ics-golang v0.0.0-20190808201353-a3394d3bcade/go.mod h1:f1P3hjG+t54/IrnXMnnw+gRmFCDR/ryj9xSQ7MPMkQw=
|
github.com/PuloV/ics-golang v0.0.0-20190808201353-a3394d3bcade/go.mod h1:f1P3hjG+t54/IrnXMnnw+gRmFCDR/ryj9xSQ7MPMkQw=
|
||||||
|
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||||
github.com/channelmeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61 h1:o64h9XF42kVEUuhuer2ehqrlX8rZmvQSU0+Vpj1rF6Q=
|
github.com/channelmeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61 h1:o64h9XF42kVEUuhuer2ehqrlX8rZmvQSU0+Vpj1rF6Q=
|
||||||
github.com/channelmeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61/go.mod h1:Rp8e0DCtEKwXFOC6JPJQVTz8tuGoGvw6Xfexggh/ed0=
|
github.com/channelmeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61/go.mod h1:Rp8e0DCtEKwXFOC6JPJQVTz8tuGoGvw6Xfexggh/ed0=
|
||||||
github.com/codegangsta/negroni v1.0.0 h1:+aYywywx4bnKXWvoWtRfJ91vC59NbEhEY03sZjQhbVY=
|
github.com/codegangsta/negroni v1.0.0 h1:+aYywywx4bnKXWvoWtRfJ91vC59NbEhEY03sZjQhbVY=
|
||||||
@ -11,14 +15,39 @@ github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom
|
|||||||
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
|
github.com/fyne-io/mobile v0.0.2 h1:eGmCR5lkFxk0PnPafGppLFRD5QODJfSVdrjhLjanOVg=
|
||||||
|
github.com/fyne-io/mobile v0.0.2/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY=
|
||||||
|
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw=
|
||||||
|
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
|
||||||
|
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
|
||||||
|
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
|
||||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||||
github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ=
|
github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ=
|
||||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||||
|
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
|
||||||
|
github.com/jchenry/jchenry v0.0.0-20200615172632-cb0bc37e6b16 h1:vVomaWqIbI/Vyb6uGE8ANmF8V3ktoLiOXdcKQLvwUc4=
|
||||||
|
github.com/jchenry/jchenry v0.0.0-20200615172632-cb0bc37e6b16/go.mod h1:WLNY6BKAzrUIfnkPA8WCUxkKchKZss4fRSVmbKZuhMg=
|
||||||
|
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
|
||||||
github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
|
github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/lucor/goinfo v0.0.0-20200401173949-526b5363a13a/go.mod h1:ORP3/rB5IsulLEBwQZCJyyV6niqmI7P4EWSmkug+1Ng=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
|
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
|
||||||
@ -30,38 +59,75 @@ github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0
|
|||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM=
|
||||||
|
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
|
||||||
|
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM=
|
||||||
|
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stripe/stripe-go v63.4.0+incompatible h1:zzZR004GZ/si7nyckn4NBhoQOViUu5VJ/sA7NT7oTSs=
|
github.com/stripe/stripe-go v63.4.0+incompatible h1:zzZR004GZ/si7nyckn4NBhoQOViUu5VJ/sA7NT7oTSs=
|
||||||
github.com/stripe/stripe-go v63.4.0+incompatible/go.mod h1:A1dQZmO/QypXmsL0T8axYZkSN/uA/T/A64pfKdBAMiY=
|
github.com/stripe/stripe-go v63.4.0+incompatible/go.mod h1:A1dQZmO/QypXmsL0T8axYZkSN/uA/T/A64pfKdBAMiY=
|
||||||
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad h1:5E5raQxcv+6CZ11RrBYQe5WRbUIWpScjh0kvHZkZIrQ=
|
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad h1:5E5raQxcv+6CZ11RrBYQe5WRbUIWpScjh0kvHZkZIrQ=
|
||||||
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
|
||||||
|
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg=
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg=
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
|
||||||
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775 h1:TC0v2RSO1u2kn1ZugjrFXkRZAEaqMN/RW+OTZkBzmLE=
|
||||||
|
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
gopkg.in/auth0.v1 v1.2.7 h1:9UCE5rKFL60rqQENmmJaGdNu7/aby8r8wVcJ83Vj5oU=
|
gopkg.in/auth0.v1 v1.2.7 h1:9UCE5rKFL60rqQENmmJaGdNu7/aby8r8wVcJ83Vj5oU=
|
||||||
gopkg.in/auth0.v1 v1.2.7/go.mod h1:1FRtMXwYDgygZcO7Of7kj/I4mf9UjHGhMHUOqNT0d0M=
|
gopkg.in/auth0.v1 v1.2.7/go.mod h1:1FRtMXwYDgygZcO7Of7kj/I4mf9UjHGhMHUOqNT0d0M=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
|
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
|
||||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||||
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
rsc.io/dbstore v0.1.1 h1:LI4gBJUwbejn0wHJWe0KTwgCM33zUVP3BsNz5y2fkEE=
|
rsc.io/dbstore v0.1.1 h1:LI4gBJUwbejn0wHJWe0KTwgCM33zUVP3BsNz5y2fkEE=
|
||||||
rsc.io/dbstore v0.1.1/go.mod h1:zI7k1PCSLg9r/T2rBM4E/SctbGmqdtt3kjQSemVh1Rs=
|
rsc.io/dbstore v0.1.1/go.mod h1:zI7k1PCSLg9r/T2rBM4E/SctbGmqdtt3kjQSemVh1Rs=
|
||||||
rsc.io/rsc v0.0.0-20180427141835-fc6202590229 h1:6s5zUknxnRp4D3GlNb7uDzlcfFVq9G2ficO+k4Bcb6w=
|
rsc.io/rsc v0.0.0-20180427141835-fc6202590229 h1:6s5zUknxnRp4D3GlNb7uDzlcfFVq9G2ficO+k4Bcb6w=
|
||||||
|
@ -1,40 +1,40 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"context"
|
// "context"
|
||||||
"log"
|
// "log"
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
// "golang.org/x/oauth2"
|
||||||
|
|
||||||
oidc "github.com/coreos/go-oidc"
|
// oidc "github.com/coreos/go-oidc"
|
||||||
)
|
// )
|
||||||
|
|
||||||
type Authenticator struct {
|
// type Authenticator struct {
|
||||||
Provider *oidc.Provider
|
// Provider *oidc.Provider
|
||||||
Config oauth2.Config
|
// Config oauth2.Config
|
||||||
Ctx context.Context
|
// Ctx context.Context
|
||||||
}
|
// }
|
||||||
|
|
||||||
func NewAuthenticator(domain, clientID, clientSecret, callback string) (*Authenticator, error) {
|
// func NewAuthenticator(domain, clientID, clientSecret, callback string) (*Authenticator, error) {
|
||||||
ctx := context.Background()
|
// ctx := context.Background()
|
||||||
|
|
||||||
provider, err := oidc.NewProvider(ctx, domain)
|
// provider, err := oidc.NewProvider(ctx, domain)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Printf("failed to get provider: %v", err)
|
// log.Printf("failed to get provider: %v", err)
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
conf := oauth2.Config{
|
// conf := oauth2.Config{
|
||||||
ClientID: clientID,
|
// ClientID: clientID,
|
||||||
ClientSecret: clientSecret,
|
// ClientSecret: clientSecret,
|
||||||
RedirectURL: callback,
|
// RedirectURL: callback,
|
||||||
Endpoint: provider.Endpoint(),
|
// Endpoint: provider.Endpoint(),
|
||||||
Scopes: []string{oidc.ScopeOpenID, "profile"},
|
// Scopes: []string{oidc.ScopeOpenID, "profile"},
|
||||||
}
|
// }
|
||||||
|
|
||||||
return &Authenticator{
|
// return &Authenticator{
|
||||||
Provider: provider,
|
// Provider: provider,
|
||||||
Config: conf,
|
// Config: conf,
|
||||||
Ctx: ctx,
|
// Ctx: ctx,
|
||||||
}, nil
|
// }, nil
|
||||||
}
|
// }
|
||||||
|
@ -1,89 +1,89 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"context"
|
// "context"
|
||||||
"log"
|
// "log"
|
||||||
"net/http"
|
// "net/http"
|
||||||
|
|
||||||
oidc "github.com/coreos/go-oidc"
|
// oidc "github.com/coreos/go-oidc"
|
||||||
)
|
// )
|
||||||
|
|
||||||
func NewCallbackHandler(c Config) http.HandlerFunc {
|
// func NewCallbackHandler(c Config) http.HandlerFunc {
|
||||||
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
// return func(w http.ResponseWriter, r *http.Request) {
|
||||||
session, err := Store.Get(r, SessionName)
|
// session, err := Store.Get(r, SessionName)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
if r.URL.Query().Get("state") != session.Values["state"] {
|
// if r.URL.Query().Get("state") != session.Values["state"] {
|
||||||
http.Error(w, "Invalid state parameter", http.StatusBadRequest)
|
// http.Error(w, "Invalid state parameter", http.StatusBadRequest)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
authenticator, err := NewAuthenticator(c.Domain, c.ClientID, c.ClientSecret, c.CallbackURL)
|
// authenticator, err := NewAuthenticator(c.Domain, c.ClientID, c.ClientSecret, c.CallbackURL)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
token, err := authenticator.Config.Exchange(context.TODO(), r.URL.Query().Get("code"))
|
// token, err := authenticator.Config.Exchange(context.TODO(), r.URL.Query().Get("code"))
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Printf("no token found: %v", err)
|
// log.Printf("no token found: %v", err)
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
// w.WriteHeader(http.StatusUnauthorized)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
rawIDToken, ok := token.Extra("id_token").(string)
|
// rawIDToken, ok := token.Extra("id_token").(string)
|
||||||
if !ok {
|
// if !ok {
|
||||||
http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError)
|
// http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
oidcConfig := &oidc.Config{
|
// oidcConfig := &oidc.Config{
|
||||||
ClientID: c.ClientID,
|
// ClientID: c.ClientID,
|
||||||
}
|
// }
|
||||||
|
|
||||||
idToken, err := authenticator.Provider.Verifier(oidcConfig).Verify(context.TODO(), rawIDToken)
|
// idToken, err := authenticator.Provider.Verifier(oidcConfig).Verify(context.TODO(), rawIDToken)
|
||||||
|
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
|
// http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Getting now the userInfo
|
// // Getting now the userInfo
|
||||||
user := User{}
|
// user := User{}
|
||||||
if err := idToken.Claims(&user); err != nil {
|
// if err := idToken.Claims(&user); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
session.Values["id_token"] = rawIDToken
|
// session.Values["id_token"] = rawIDToken
|
||||||
session.Values["access_token"] = token.AccessToken
|
// session.Values["access_token"] = token.AccessToken
|
||||||
session.Values["profile"] = user
|
// session.Values["profile"] = user
|
||||||
err = session.Save(r, w)
|
// err = session.Save(r, w)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
// if application ID is non existent, and therefore does not have a tenant
|
// // if application ID is non existent, and therefore does not have a tenant
|
||||||
// Create or associate?
|
// // Create or associate?
|
||||||
// Create:
|
// // Create:
|
||||||
// - Create Tenant
|
// // - Create Tenant
|
||||||
// - Specify plan
|
// // - Specify plan
|
||||||
// - Specify payment info
|
// // - Specify payment info
|
||||||
// - Associate Tenant
|
// // - Associate Tenant
|
||||||
// - by email address domain?
|
// // - by email address domain?
|
||||||
//set tenant ID on application ID in App Metadata on user
|
// //set tenant ID on application ID in App Metadata on user
|
||||||
|
|
||||||
// if c.CallbackFunc != nil {
|
// // if c.CallbackFunc != nil {
|
||||||
// c.CallbackFunc(c, user)
|
// // c.CallbackFunc(c, user)
|
||||||
// } else {
|
// // } else {
|
||||||
// Redirect to logged in page
|
// // Redirect to logged in page
|
||||||
http.Redirect(w, r, c.RedirectURL, http.StatusSeeOther)
|
// http.Redirect(w, r, c.RedirectURL, http.StatusSeeOther)
|
||||||
// }
|
// // }
|
||||||
|
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
@ -1,36 +1,36 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"fmt"
|
// "fmt"
|
||||||
"os"
|
// "os"
|
||||||
)
|
// )
|
||||||
|
|
||||||
type Config struct {
|
// type Config struct {
|
||||||
Domain string
|
// Domain string
|
||||||
ClientID string
|
// ClientID string
|
||||||
ClientSecret string
|
// ClientSecret string
|
||||||
ManagementClientID string
|
// ManagementClientID string
|
||||||
ManagementClientSecret string
|
// ManagementClientSecret string
|
||||||
|
|
||||||
CallbackURL string
|
// CallbackURL string
|
||||||
RedirectURL string
|
// RedirectURL string
|
||||||
}
|
// }
|
||||||
|
|
||||||
func FromEnv() Config {
|
// func FromEnv() Config {
|
||||||
return Config{
|
// return Config{
|
||||||
Domain: os.Getenv("AUTH_DOMAIN"),
|
// Domain: os.Getenv("AUTH_DOMAIN"),
|
||||||
ClientID: os.Getenv("AUTH_CLIENT_ID"),
|
// ClientID: os.Getenv("AUTH_CLIENT_ID"),
|
||||||
ClientSecret: os.Getenv("AUTH_CLIENT_SECRET"),
|
// ClientSecret: os.Getenv("AUTH_CLIENT_SECRET"),
|
||||||
ManagementClientID: os.Getenv("AUTH_MGMT_CLIENT_ID"),
|
// ManagementClientID: os.Getenv("AUTH_MGMT_CLIENT_ID"),
|
||||||
ManagementClientSecret: os.Getenv("AUTH_MGMT_CLIENT_SECRET"),
|
// ManagementClientSecret: os.Getenv("AUTH_MGMT_CLIENT_SECRET"),
|
||||||
|
|
||||||
CallbackURL: os.Getenv("AUTH_CALLBACK_URL"),
|
// CallbackURL: os.Getenv("AUTH_CALLBACK_URL"),
|
||||||
RedirectURL: "/user",
|
// RedirectURL: "/user",
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func PrintConfig() {
|
// func PrintConfig() {
|
||||||
fmt.Printf("%#v\n", FromEnv())
|
// fmt.Printf("%#v\n", FromEnv())
|
||||||
}
|
// }
|
||||||
|
|
||||||
type CallbackFunc func(c Config, u User) error
|
// type CallbackFunc func(c Config, u User) error
|
||||||
|
@ -2,42 +2,42 @@
|
|||||||
|
|
||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"crypto/rand"
|
// "crypto/rand"
|
||||||
"encoding/base64"
|
// "encoding/base64"
|
||||||
"net/http"
|
// "net/http"
|
||||||
)
|
// )
|
||||||
|
|
||||||
func NewLoginHandler(c Config) http.HandlerFunc {
|
// func NewLoginHandler(c Config) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
// return func(w http.ResponseWriter, r *http.Request) {
|
||||||
// Generate random state
|
// // Generate random state
|
||||||
b := make([]byte, 32)
|
// b := make([]byte, 32)
|
||||||
_, err := rand.Read(b)
|
// _, err := rand.Read(b)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
state := base64.StdEncoding.EncodeToString(b)
|
// state := base64.StdEncoding.EncodeToString(b)
|
||||||
|
|
||||||
session, err := Store.Get(r, SessionName)
|
// session, err := Store.Get(r, SessionName)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
session.Values["state"] = state
|
// session.Values["state"] = state
|
||||||
err = session.Save(r, w)
|
// err = session.Save(r, w)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
authenticator, err := NewAuthenticator(c.Domain, c.ClientID, c.ClientSecret, c.CallbackURL)
|
// authenticator, err := NewAuthenticator(c.Domain, c.ClientID, c.ClientSecret, c.CallbackURL)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
http.Redirect(w, r, authenticator.Config.AuthCodeURL(state), http.StatusTemporaryRedirect)
|
// http.Redirect(w, r, authenticator.Config.AuthCodeURL(state), http.StatusTemporaryRedirect)
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
// }
|
||||||
|
@ -1,43 +1,43 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"net/http"
|
// "net/http"
|
||||||
"net/url"
|
// "net/url"
|
||||||
)
|
// )
|
||||||
|
|
||||||
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
|
// func LogoutHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if cook, err := r.Cookie(SessionName); err == nil {
|
// if cook, err := r.Cookie(SessionName); err == nil {
|
||||||
cook.MaxAge = -1
|
// cook.MaxAge = -1
|
||||||
http.SetCookie(w, cook)
|
// http.SetCookie(w, cook)
|
||||||
}
|
// }
|
||||||
|
|
||||||
domain := "dev-pb4s8m55.auth0.com"
|
// domain := "dev-pb4s8m55.auth0.com"
|
||||||
|
|
||||||
// var Url *url.URL
|
// // var Url *url.URL
|
||||||
URL, err := url.Parse("https://" + domain)
|
// URL, err := url.Parse("https://" + domain)
|
||||||
|
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
panic(err.Error())
|
// panic(err.Error())
|
||||||
}
|
// }
|
||||||
|
|
||||||
session, err := Store.Get(r, SessionName)
|
// session, err := Store.Get(r, SessionName)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
session.Options.MaxAge = -1
|
// session.Options.MaxAge = -1
|
||||||
|
|
||||||
err = session.Save(r, w)
|
// err = session.Save(r, w)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
// }
|
||||||
|
|
||||||
URL.Path += "/v2/logout"
|
// URL.Path += "/v2/logout"
|
||||||
parameters := url.Values{}
|
// parameters := url.Values{}
|
||||||
parameters.Add("returnTo", "http://localhost:3000")
|
// parameters.Add("returnTo", "http://localhost:3000")
|
||||||
parameters.Add("client_id", "ae1e02bTwXA35O3r3Xxk4kbRf31j5ge9")
|
// parameters.Add("client_id", "ae1e02bTwXA35O3r3Xxk4kbRf31j5ge9")
|
||||||
URL.RawQuery = parameters.Encode()
|
// URL.RawQuery = parameters.Encode()
|
||||||
|
|
||||||
http.Redirect(w, r, URL.String(), http.StatusTemporaryRedirect)
|
// http.Redirect(w, r, URL.String(), http.StatusTemporaryRedirect)
|
||||||
}
|
// }
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import "net/http"
|
// import "net/http"
|
||||||
|
|
||||||
func IsAuthenticated(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
// func IsAuthenticated(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||||
|
|
||||||
session, err := Store.Get(r, SessionName)
|
// session, err := Store.Get(r, SessionName)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
if _, ok := session.Values["profile"]; !ok {
|
// if _, ok := session.Values["profile"]; !ok {
|
||||||
//TODO allow customization of redirect
|
// //TODO allow customization of redirect
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
// http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
} else {
|
// } else {
|
||||||
next(w, r)
|
// next(w, r)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
@ -1,39 +1,38 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"net/http"
|
// "github.com/jchenry/x/internal/http"
|
||||||
|
// )
|
||||||
|
|
||||||
"github.com/codegangsta/negroni"
|
// func Service(c Config) ServiceInstance {
|
||||||
_http "github.com/jchenry/jchenry/internal/http"
|
// return ServiceInstance{c: c}
|
||||||
"gopkg.in/auth0.v1/management"
|
// }
|
||||||
)
|
|
||||||
|
|
||||||
func Service(c Config) ServiceInstance {
|
// type ServiceInstance struct {
|
||||||
return ServiceInstance{c: c}
|
// c Config
|
||||||
}
|
// }
|
||||||
|
|
||||||
type ServiceInstance struct {
|
// func (si ServiceInstance) Register(m *http.Mux) {
|
||||||
c Config
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
func (si ServiceInstance) Register(uriBase string, s *_http.Server) {
|
// func (si ServiceInstance) Register(uriBase string, s *_http.Server) {
|
||||||
|
|
||||||
s.Get(uriBase+"/login", "login endpoint", http.HandlerFunc(NewLoginHandler(si.c)))
|
// s.Get(uriBase+"/login", "login endpoint", http.HandlerFunc(NewLoginHandler(si.c)))
|
||||||
s.Get(uriBase+"/logout", "logout endpoint", http.HandlerFunc(LogoutHandler))
|
// s.Get(uriBase+"/logout", "logout endpoint", http.HandlerFunc(LogoutHandler))
|
||||||
s.Get(uriBase+"/callback", "oidc callback", http.HandlerFunc(NewCallbackHandler(si.c)))
|
// s.Get(uriBase+"/callback", "oidc callback", http.HandlerFunc(NewCallbackHandler(si.c)))
|
||||||
s.Get(uriBase+"/user", "user info endpoint", negroni.New(
|
// s.Get(uriBase+"/user", "user info endpoint", negroni.New(
|
||||||
negroni.HandlerFunc(IsAuthenticated),
|
// negroni.HandlerFunc(IsAuthenticated),
|
||||||
negroni.Wrap(http.HandlerFunc(UserHandler)),
|
// negroni.Wrap(http.HandlerFunc(UserHandler)),
|
||||||
))
|
// ))
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (si ServiceInstance) UpdateUser(u User) error {
|
// func (si ServiceInstance) UpdateUser(u User) error {
|
||||||
m, err := management.New(si.c.Domain, si.c.ManagementClientID, si.c.ManagementClientSecret)
|
// m, err := management.New(si.c.Domain, si.c.ManagementClientID, si.c.ManagementClientSecret)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
um := management.NewUserManager(m)
|
// um := management.NewUserManager(m)
|
||||||
|
|
||||||
return um.Update(u.ID, &management.User{AppMetadata: u.Apps})
|
// return um.Update(u.ID, &management.User{AppMetadata: u.Apps})
|
||||||
}
|
// }
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"encoding/gob"
|
// "encoding/gob"
|
||||||
|
|
||||||
"github.com/gorilla/sessions"
|
// "github.com/gorilla/sessions"
|
||||||
)
|
// )
|
||||||
|
|
||||||
const SessionName = "auth-session"
|
// const SessionName = "auth-session"
|
||||||
|
|
||||||
var (
|
// var (
|
||||||
Store *sessions.FilesystemStore
|
// Store *sessions.FilesystemStore
|
||||||
)
|
// )
|
||||||
|
|
||||||
func Init() error {
|
// func Init() error {
|
||||||
Store = sessions.NewFilesystemStore("", []byte("something-very-secret"))
|
// Store = sessions.NewFilesystemStore("", []byte("something-very-secret"))
|
||||||
gob.Register(User{})
|
// gob.Register(User{})
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
@ -1,28 +1,28 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"net/http"
|
// "net/http"
|
||||||
|
|
||||||
jchenry_http "github.com/jchenry/jchenry/internal/http"
|
// jchenry_http "github.com/jchenry/x/internal/http"
|
||||||
)
|
// )
|
||||||
|
|
||||||
func UserHandler(w http.ResponseWriter, r *http.Request) {
|
// func UserHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
session, err := Store.Get(r, SessionName)
|
// session, err := Store.Get(r, SessionName)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
jchenry_http.RenderTemplate(w, "user", session.Values["profile"])
|
// jchenry_http.RenderTemplate(w, "user", session.Values["profile"])
|
||||||
}
|
// }
|
||||||
|
|
||||||
type User struct {
|
// type User struct {
|
||||||
ID string `json:"sub"`
|
// ID string `json:"sub"`
|
||||||
Email string `json:"email"`
|
// Email string `json:"email"`
|
||||||
FirstName string `json:"given_name"`
|
// FirstName string `json:"given_name"`
|
||||||
LastName string `json:"family_name"`
|
// LastName string `json:"family_name"`
|
||||||
Picture string `json:"picture"`
|
// Picture string `json:"picture"`
|
||||||
Nickname string `json:"nickname"`
|
// Nickname string `json:"nickname"`
|
||||||
Apps map[string]interface{} `json:"app_metadata,omitempty"`
|
// Apps map[string]interface{} `json:"app_metadata,omitempty"`
|
||||||
}
|
// }
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package crud
|
package crud
|
||||||
|
|
||||||
import (
|
import "github.com/jchenry/jchenry/pkg/db"
|
||||||
"github.com/jchenry/jchenry/pkg/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
// Find returns a pointer to an array of the results found based on params
|
// Find returns a pointer to an array of the results found based on params
|
||||||
|
79
internal/gopher/client.go
Normal file
79
internal/gopher/client.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package gopher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RFC 1436 types
|
||||||
|
Text byte = '0'
|
||||||
|
Submenu byte = '1'
|
||||||
|
Nameserver byte = '2'
|
||||||
|
Error byte = '3'
|
||||||
|
Binhex byte = '4'
|
||||||
|
DOS byte = '5'
|
||||||
|
UUencode byte = '6'
|
||||||
|
Search byte = '7'
|
||||||
|
Telnet byte = '8'
|
||||||
|
Binary byte = '9'
|
||||||
|
Mirror byte = '+'
|
||||||
|
Gif byte = 'g'
|
||||||
|
Image byte = 'I'
|
||||||
|
Telnet3270 byte = 'T'
|
||||||
|
|
||||||
|
// UnRFC'd Extensions
|
||||||
|
Doc byte = 'd'
|
||||||
|
Html byte = 'h'
|
||||||
|
Info byte = 'i'
|
||||||
|
Sound byte = 's'
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
Socket net.Conn
|
||||||
|
in *bufio.Reader
|
||||||
|
out *bufio.Writer
|
||||||
|
init sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Select(selector string) (m Menu, err error) {
|
||||||
|
c.init.Do(func() {
|
||||||
|
c.in = bufio.NewReader(c.Socket)
|
||||||
|
c.out = bufio.NewWriter(c.Socket)
|
||||||
|
})
|
||||||
|
c.out.WriteString(selector)
|
||||||
|
|
||||||
|
for {
|
||||||
|
if l, _, err := c.in.ReadLine(); err == nil {
|
||||||
|
s := Selector{}
|
||||||
|
s.Type = l[0]
|
||||||
|
bs := bytes.Split(l[1:], []byte{'\t'})
|
||||||
|
s.Display = string(bs[0])
|
||||||
|
s.Path = string(bs[1])
|
||||||
|
s.Hostname = string(bytes.Join(bs[2:3], []byte{':'}))
|
||||||
|
m = append(m, s)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// s := Selector{
|
||||||
|
// Type: Text,
|
||||||
|
// Display: "",
|
||||||
|
// Hostname: "",
|
||||||
|
// Path: "",
|
||||||
|
// }
|
||||||
|
return Menu{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Menu []Selector
|
||||||
|
|
||||||
|
type Selector struct {
|
||||||
|
Type byte
|
||||||
|
Display string
|
||||||
|
Path string
|
||||||
|
Hostname string
|
||||||
|
Port string
|
||||||
|
}
|
14
internal/http/error.go
Normal file
14
internal/http/error.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// Error is an error wrapper for the standard HTTP error codes
|
||||||
|
type Error int
|
||||||
|
|
||||||
|
func (e Error) Error() string {
|
||||||
|
return http.StatusText(int(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Error) Code() int {
|
||||||
|
return int(e)
|
||||||
|
}
|
8
internal/http/mux.go
Normal file
8
internal/http/mux.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
type Mux interface {
|
||||||
|
Handle(pattern string, handler http.Handler)
|
||||||
|
HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
|
// "log"
|
||||||
"net/http"
|
"net/http"
|
||||||
go_http "net/http"
|
go_http "net/http"
|
||||||
)
|
)
|
||||||
@ -17,86 +18,87 @@ type Router interface {
|
|||||||
AddHandler(method, path string, handler go_http.Handler)
|
AddHandler(method, path string, handler go_http.Handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Service interface {
|
// type Service interface {
|
||||||
Register(uriBase string, restServer *Server)
|
// Register(uriBase string, restServer *Server)
|
||||||
}
|
// }
|
||||||
|
|
||||||
type ServiceFunc func(uriBase string, restServer *Server)
|
// type ServiceFunc func(uriBase string, restServer *Server)
|
||||||
|
|
||||||
func (f ServiceFunc) Register(uriBase string, restServer *Server) {
|
// func (f ServiceFunc) Register(uriBase string, restServer *Server) {
|
||||||
f(uriBase, restServer)
|
// f(uriBase, restServer)
|
||||||
}
|
// }
|
||||||
|
|
||||||
var docString = "%s \t%s\t- %s"
|
// var docString = "%s \t%s\t- %s"
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
router Router
|
router Router
|
||||||
middleware Middleware
|
middleware Middleware
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(m Middleware, r Router) *Server {
|
// func NewServer(m Middleware, r Router) *Server {
|
||||||
s := &Server{
|
// s := &Server{
|
||||||
router: r,
|
// router: r,
|
||||||
middleware: m,
|
// middleware: m,
|
||||||
}
|
// }
|
||||||
|
|
||||||
s.middleware.UseHandler(s.router)
|
// s.middleware.UseHandler(s.router)
|
||||||
|
|
||||||
return s
|
// return s
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (r *Server) Get(path string, documentation string, handle go_http.Handler) *Server {
|
func (r *Server) Get(path string, documentation string, handle go_http.Handler) *Server {
|
||||||
r.handle("GET", path, documentation, handle)
|
r.handle("GET", path, documentation, handle)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
func (r *Server) Patch(path string, documentation string, handle go_http.Handler) *Server {
|
|
||||||
r.handle("PATCH", path, documentation, handle)
|
|
||||||
|
|
||||||
return r
|
// func (r *Server) Patch(path string, documentation string, handle go_http.Handler) *Server {
|
||||||
}
|
// r.handle("PATCH", path, documentation, handle)
|
||||||
|
|
||||||
|
// return r
|
||||||
|
// }
|
||||||
func (r *Server) Post(path string, documentation string, handle go_http.Handler) *Server {
|
func (r *Server) Post(path string, documentation string, handle go_http.Handler) *Server {
|
||||||
r.handle("POST", path, documentation, handle)
|
r.handle("POST", path, documentation, handle)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
func (r *Server) Put(path string, documentation string, handle go_http.Handler) *Server {
|
|
||||||
r.handle("PUT", path, documentation, handle)
|
|
||||||
|
|
||||||
return r
|
// func (r *Server) Put(path string, documentation string, handle go_http.Handler) *Server {
|
||||||
}
|
// r.handle("PUT", path, documentation, handle)
|
||||||
func (r *Server) Delete(path string, documentation string, handle go_http.Handler) *Server {
|
|
||||||
r.handle("DELETE", path, documentation, handle)
|
|
||||||
|
|
||||||
return r
|
// return r
|
||||||
}
|
// }
|
||||||
|
// func (r *Server) Delete(path string, documentation string, handle go_http.Handler) *Server {
|
||||||
|
// r.handle("DELETE", path, documentation, handle)
|
||||||
|
|
||||||
|
// return r
|
||||||
|
// }
|
||||||
func (r *Server) handle(method, path string, documentation string, handler go_http.Handler) {
|
func (r *Server) handle(method, path string, documentation string, handler go_http.Handler) {
|
||||||
log.Printf(docString, method, path, documentation)
|
// log.Printf(docString, method, path, documentation)
|
||||||
r.router.AddHandler(method, path, handler)
|
r.router.AddHandler(method, path, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Server) Banner(banner string) *Server {
|
// func (r *Server) Banner(banner string) *Server {
|
||||||
log.Printf(banner)
|
// log.Printf(banner)
|
||||||
return r
|
// return r
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (r *Server) Service(basePath string, service Service) *Server {
|
// func (r *Server) Service(basePath string, service Service) *Server {
|
||||||
service.Register(basePath, r)
|
// service.Register(basePath, r)
|
||||||
return r
|
// return r
|
||||||
}
|
// }
|
||||||
func (r *Server) Static(path string, root go_http.FileSystem) *Server {
|
// func (r *Server) Static(path string, root go_http.FileSystem) *Server {
|
||||||
r.router.ServeFiles(path, root)
|
// r.router.ServeFiles(path, root)
|
||||||
return r
|
// return r
|
||||||
}
|
// }
|
||||||
func (r *Server) Middleware(handler go_http.Handler) *Server {
|
// func (r *Server) Middleware(handler go_http.Handler) *Server {
|
||||||
r.middleware.UseHandler(handler)
|
// r.middleware.UseHandler(handler)
|
||||||
return r
|
// return r
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (r *Server) Run(addr string) {
|
// func (r *Server) Run(addr string) {
|
||||||
log.Printf("listening on %s", addr)
|
// log.Printf("listening on %s", addr)
|
||||||
log.Fatal(http.ListenAndServe(addr, r.middleware))
|
// log.Fatal(http.ListenAndServe(addr, r.middleware))
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (r *Server) ServeHTTP(w go_http.ResponseWriter, req *go_http.Request) {
|
// func (r *Server) ServeHTTP(w go_http.ResponseWriter, req *go_http.Request) {
|
||||||
r.middleware.ServeHTTP(w, req)
|
// r.middleware.ServeHTTP(w, req)
|
||||||
}
|
// }
|
||||||
|
@ -1,34 +1,34 @@
|
|||||||
package http_test
|
package http_test
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"os"
|
// "os"
|
||||||
|
|
||||||
"github.com/codegangsta/negroni"
|
// "github.com/codegangsta/negroni"
|
||||||
"github.com/jchenry/jchenry/http"
|
// "github.com/jchenry/jchenry/http"
|
||||||
"github.com/jchenry/jchenry/rest"
|
// "github.com/jchenry/jchenry/rest"
|
||||||
)
|
// )
|
||||||
|
|
||||||
func ExampleServer() {
|
// func ExampleServer() {
|
||||||
type contact struct {
|
// type contact struct {
|
||||||
ID int64 `json:"id"`
|
// ID int64 `json:"id"`
|
||||||
First string `json:"firstName"`
|
// First string `json:"firstName"`
|
||||||
Last string `json:"lastName"`
|
// Last string `json:"lastName"`
|
||||||
Email string `json:"emailAddress"`
|
// Email string `json:"emailAddress"`
|
||||||
}
|
// }
|
||||||
|
|
||||||
s := http.NewServer(
|
// s := http.NewServer(
|
||||||
negroni.Classic(),
|
// negroni.Classic(),
|
||||||
http.NewJulienschmidtHTTPRouter()).
|
// http.NewJulienschmidtHTTPRouter()).
|
||||||
Service("",
|
// Service("",
|
||||||
rest.Collection(new(contact),
|
// rest.Collection(new(contact),
|
||||||
nil,
|
// nil,
|
||||||
),
|
// ),
|
||||||
)
|
// )
|
||||||
|
|
||||||
port := os.Getenv("PORT")
|
// port := os.Getenv("PORT")
|
||||||
if port == "" {
|
// if port == "" {
|
||||||
port = "8080"
|
// port = "8080"
|
||||||
}
|
// }
|
||||||
|
|
||||||
s.Run(":" + port)
|
// s.Run(":" + port)
|
||||||
}
|
// }
|
||||||
|
@ -26,3 +26,13 @@ package http
|
|||||||
// MethodConnect = "CONNECT"
|
// MethodConnect = "CONNECT"
|
||||||
// MethodOptions = "OPTIONS"
|
// MethodOptions = "OPTIONS"
|
||||||
// MethodTrace = "TRACE"
|
// MethodTrace = "TRACE"
|
||||||
|
|
||||||
|
type Service interface {
|
||||||
|
Register(m *Mux) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceFunc func(m *Mux) error
|
||||||
|
|
||||||
|
func (s ServiceFunc) Register(m *Mux) error {
|
||||||
|
return s(m)
|
||||||
|
}
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
package payments
|
package payments
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"fmt"
|
// "fmt"
|
||||||
"os"
|
// "os"
|
||||||
)
|
// )
|
||||||
|
|
||||||
type Config struct {
|
// type Config struct {
|
||||||
StripeKey string
|
// StripeKey string
|
||||||
StripeProductID string
|
// StripeProductID string
|
||||||
RedirectURL string
|
// RedirectURL string
|
||||||
TenantSetup func(subscriptionID, customerID string) (tenantID string)
|
// TenantSetup func(subscriptionID, customerID string) (tenantID string)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func FromEnv() Config {
|
// func FromEnv() Config {
|
||||||
return Config{
|
// return Config{
|
||||||
StripeKey: os.Getenv("STRIPE_KEY"),
|
// StripeKey: os.Getenv("STRIPE_KEY"),
|
||||||
StripeProductID: os.Getenv("STRIPE_PRODUCT_ID"),
|
// StripeProductID: os.Getenv("STRIPE_PRODUCT_ID"),
|
||||||
RedirectURL: "/",
|
// RedirectURL: "/",
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func PrintConfig() {
|
// func PrintConfig() {
|
||||||
fmt.Printf("%#v\n", FromEnv())
|
// fmt.Printf("%#v\n", FromEnv())
|
||||||
}
|
// }
|
||||||
|
@ -1,27 +1,26 @@
|
|||||||
package payments
|
package payments
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"net/http"
|
// "net/http"
|
||||||
|
|
||||||
"github.com/jchenry/jchenry/internal/auth"
|
// "github.com/jchenry/x/internal/auth"
|
||||||
)
|
// )
|
||||||
|
|
||||||
func HasTenantAndSubscription(productID string) func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
// func HasTenantAndSubscription(productID string) func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||||
return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
// return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||||
|
// session, err := auth.Store.Get(r, auth.SessionName)
|
||||||
|
// if err != nil {
|
||||||
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
session, err := auth.Store.Get(r, auth.SessionName)
|
// if u, ok := session.Values["profile"]; ok {
|
||||||
if err != nil {
|
// user := u.(auth.User)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
// if _, exist := user.Apps[productID]; exist {
|
||||||
return
|
// next(w, r)
|
||||||
}
|
// } else {
|
||||||
|
// http.Redirect(w, r, "/subscription", http.StatusSeeOther)
|
||||||
if u, ok := session.Values["profile"]; ok {
|
// }
|
||||||
user := u.(auth.User)
|
// }
|
||||||
if _, exist := user.Apps[productID]; exist {
|
// }
|
||||||
next(w, r)
|
// }
|
||||||
} else {
|
|
||||||
http.Redirect(w, r, "/subscription", http.StatusSeeOther)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,121 +1,121 @@
|
|||||||
package payments
|
package payments
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"fmt"
|
// "fmt"
|
||||||
"net/http"
|
// "net/http"
|
||||||
|
|
||||||
"github.com/codegangsta/negroni"
|
// "github.com/codegangsta/negroni"
|
||||||
"github.com/jchenry/jchenry/internal/auth"
|
// "github.com/jchenry/x/internal/auth"
|
||||||
_http "github.com/jchenry/jchenry/internal/http"
|
// _http "github.com/jchenry/x/internal/http"
|
||||||
"github.com/stripe/stripe-go"
|
// "github.com/stripe/stripe-go"
|
||||||
"github.com/stripe/stripe-go/client"
|
// "github.com/stripe/stripe-go/client"
|
||||||
"github.com/stripe/stripe-go/customer"
|
// "github.com/stripe/stripe-go/customer"
|
||||||
"github.com/stripe/stripe-go/plan"
|
// "github.com/stripe/stripe-go/plan"
|
||||||
"github.com/stripe/stripe-go/product"
|
// "github.com/stripe/stripe-go/product"
|
||||||
"github.com/stripe/stripe-go/sub"
|
// "github.com/stripe/stripe-go/sub"
|
||||||
)
|
// )
|
||||||
|
|
||||||
func Service(c Config, auth *auth.ServiceInstance) ServiceInstance {
|
// func Service(c Config, auth *auth.ServiceInstance) ServiceInstance {
|
||||||
stripe.Key = c.StripeKey
|
// stripe.Key = c.StripeKey
|
||||||
sc := &client.API{}
|
// sc := &client.API{}
|
||||||
sc.Init(c.StripeKey, nil)
|
// sc.Init(c.StripeKey, nil)
|
||||||
return ServiceInstance{
|
// return ServiceInstance{
|
||||||
c: c,
|
// c: c,
|
||||||
stripe: sc,
|
// stripe: sc,
|
||||||
auth: auth,
|
// auth: auth,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
type ServiceInstance struct {
|
// type ServiceInstance struct {
|
||||||
c Config
|
// c Config
|
||||||
stripe *client.API
|
// stripe *client.API
|
||||||
auth *auth.ServiceInstance
|
// auth *auth.ServiceInstance
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (si ServiceInstance) Register(uriBase string, s *_http.Server) {
|
// func (si ServiceInstance) Register(uriBase string, s *_http.Server) {
|
||||||
s.Get(uriBase+"/subscription", "subscription info endpoint", negroni.New(
|
// s.Get(uriBase+"/subscription", "subscription info endpoint", negroni.New(
|
||||||
negroni.HandlerFunc(auth.IsAuthenticated),
|
// negroni.HandlerFunc(auth.IsAuthenticated),
|
||||||
negroni.Wrap(http.HandlerFunc(si.subscriptionHandler)),
|
// negroni.Wrap(http.HandlerFunc(si.subscriptionHandler)),
|
||||||
)).Post(uriBase+"/subscription", "subscription payment endpoint", negroni.New(
|
// )).Post(uriBase+"/subscription", "subscription payment endpoint", negroni.New(
|
||||||
negroni.HandlerFunc(auth.IsAuthenticated),
|
// negroni.HandlerFunc(auth.IsAuthenticated),
|
||||||
negroni.Wrap(http.HandlerFunc(si.paymentHandler)),
|
// negroni.Wrap(http.HandlerFunc(si.paymentHandler)),
|
||||||
))
|
// ))
|
||||||
|
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (si ServiceInstance) subscriptionHandler(w http.ResponseWriter, r *http.Request) {
|
// func (si ServiceInstance) subscriptionHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
prod, _ := product.Get(si.c.StripeProductID, nil)
|
// prod, _ := product.Get(si.c.StripeProductID, nil)
|
||||||
|
|
||||||
params := &stripe.PlanListParams{
|
// params := &stripe.PlanListParams{
|
||||||
Product: &si.c.StripeProductID,
|
// Product: &si.c.StripeProductID,
|
||||||
}
|
// }
|
||||||
|
|
||||||
it := plan.List(params)
|
// it := plan.List(params)
|
||||||
var plans []stripe.Plan
|
// var plans []stripe.Plan
|
||||||
for it.Next() {
|
// for it.Next() {
|
||||||
plans = append(plans, *it.Plan())
|
// plans = append(plans, *it.Plan())
|
||||||
}
|
// }
|
||||||
_http.RenderTemplate(w, "subscription", offering{Product: *prod, Plans: plans})
|
// _http.RenderTemplate(w, "subscription", offering{Product: *prod, Plans: plans})
|
||||||
}
|
// }
|
||||||
|
|
||||||
type offering struct {
|
// type offering struct {
|
||||||
Product stripe.Product
|
// Product stripe.Product
|
||||||
Plans []stripe.Plan
|
// Plans []stripe.Plan
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (si ServiceInstance) paymentHandler(w http.ResponseWriter, r *http.Request) {
|
// func (si ServiceInstance) paymentHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
session, err := auth.Store.Get(r, auth.SessionName)
|
// session, err := auth.Store.Get(r, auth.SessionName)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
if u, ok := session.Values["profile"]; ok {
|
// if u, ok := session.Values["profile"]; ok {
|
||||||
user := u.(auth.User)
|
// user := u.(auth.User)
|
||||||
r.ParseForm()
|
// r.ParseForm()
|
||||||
|
|
||||||
params := &stripe.CustomerParams{
|
// params := &stripe.CustomerParams{
|
||||||
Email: stripe.String(user.Email),
|
// Email: stripe.String(user.Email),
|
||||||
Name: stripe.String(fmt.Sprintf("%s, %s", user.LastName, user.FirstName)),
|
// Name: stripe.String(fmt.Sprintf("%s, %s", user.LastName, user.FirstName)),
|
||||||
}
|
// }
|
||||||
params.SetSource(r.PostFormValue("stripeToken"))
|
// params.SetSource(r.PostFormValue("stripeToken"))
|
||||||
cus, err := customer.New(params)
|
// cus, err := customer.New(params)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
p := &stripe.SubscriptionParams{
|
// p := &stripe.SubscriptionParams{
|
||||||
Customer: stripe.String(cus.ID),
|
// Customer: stripe.String(cus.ID),
|
||||||
Items: []*stripe.SubscriptionItemsParams{
|
// Items: []*stripe.SubscriptionItemsParams{
|
||||||
{
|
// {
|
||||||
Plan: stripe.String(r.PostFormValue("plan")),
|
// Plan: stripe.String(r.PostFormValue("plan")),
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
}
|
// }
|
||||||
s, err := sub.New(p)
|
// s, err := sub.New(p)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
if si.c.TenantSetup == nil {
|
// if si.c.TenantSetup == nil {
|
||||||
panic("need code to setup the tenant")
|
// panic("need code to setup the tenant")
|
||||||
}
|
// }
|
||||||
|
|
||||||
if user.Apps == nil {
|
// if user.Apps == nil {
|
||||||
user.Apps = map[string]interface{}{}
|
// user.Apps = map[string]interface{}{}
|
||||||
}
|
// }
|
||||||
user.Apps[si.c.StripeProductID] = si.c.TenantSetup(s.ID, user.ID)
|
// user.Apps[si.c.StripeProductID] = si.c.TenantSetup(s.ID, user.ID)
|
||||||
err = si.auth.UpdateUser(user)
|
// err = si.auth.UpdateUser(user)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
http.Redirect(w, r, si.c.RedirectURL, http.StatusSeeOther)
|
// http.Redirect(w, r, si.c.RedirectURL, http.StatusSeeOther)
|
||||||
|
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
@ -1,204 +0,0 @@
|
|||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
_http "github.com/jchenry/jchenry/internal/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
//IDPathParameter represents the entity's id in the parameter map IDPathParameter = "id"
|
|
||||||
IDPathParameter = "id"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Collection - A Restful Collection interface backed by crud.CrudService
|
|
||||||
type CollectionInstance struct {
|
|
||||||
basePath string
|
|
||||||
name string
|
|
||||||
instanceType reflect.Type
|
|
||||||
service CollectionStore
|
|
||||||
}
|
|
||||||
|
|
||||||
type CollectionStore interface {
|
|
||||||
// Find returns a pointer to an array of the results found based on params
|
|
||||||
// or an error
|
|
||||||
Find(entityArrPtr interface{}, params map[string]interface{}) error
|
|
||||||
// Create returns the identifier for the newly accepted entity, or error
|
|
||||||
Create(entityPtr interface{}) error
|
|
||||||
// Update returns the id of the newly updated entity, or error
|
|
||||||
Update(entityPtr interface{}) error
|
|
||||||
// Delete returns whether the entity, specified by id, was successfully deleted
|
|
||||||
// or error
|
|
||||||
Delete(entityPtr interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// type GetIDPathParameter func(*http.Request)
|
|
||||||
|
|
||||||
// Collection - Create a new instance of RESTCollection
|
|
||||||
func Collection(entityPtr interface{}, service CollectionStore) *CollectionInstance {
|
|
||||||
t := reflect.TypeOf(entityPtr).Elem()
|
|
||||||
return &CollectionInstance{
|
|
||||||
name: strings.ToLower(t.Name()),
|
|
||||||
instanceType: t,
|
|
||||||
service: service,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (collection *CollectionInstance) Register(uriBase string, restServer *_http.Server) {
|
|
||||||
plural := properPlural(collection.name)
|
|
||||||
|
|
||||||
urlBase := uriBase + "/" + plural //collection.name + "s"
|
|
||||||
restServer.
|
|
||||||
Post(urlBase, "create a "+collection.name, http.HandlerFunc(collection.create)).
|
|
||||||
Put(urlBase+"/:"+IDPathParameter, "update a "+collection.name, http.HandlerFunc(collection.update)).
|
|
||||||
Delete(urlBase+"/:"+IDPathParameter, "delete a "+collection.name, http.HandlerFunc(collection.remove)).
|
|
||||||
Get(urlBase+"/:"+IDPathParameter, "get a "+collection.name+" by id", http.HandlerFunc(collection.find)).
|
|
||||||
Get(urlBase, "get "+collection.name+"s", http.HandlerFunc(collection.find))
|
|
||||||
}
|
|
||||||
|
|
||||||
func properPlural(word string) string {
|
|
||||||
if strings.HasSuffix(word, "s") {
|
|
||||||
return word
|
|
||||||
} else if strings.HasSuffix(word, "y") {
|
|
||||||
return word[:len(word)-1] + "ies"
|
|
||||||
} else {
|
|
||||||
return word + "s"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (collection *CollectionInstance) create(response http.ResponseWriter, request *http.Request) {
|
|
||||||
entityPtr := reflect.New(collection.instanceType).Interface() //collection.instanceProviderPtr.NewInstance()
|
|
||||||
|
|
||||||
err := _http.ReadEntity(request, entityPtr)
|
|
||||||
if err != nil {
|
|
||||||
_http.WriteErrorResponse(response, http.StatusBadRequest, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = collection.service.Create(entityPtr)
|
|
||||||
if err != nil {
|
|
||||||
_http.WriteErrorResponse(response, http.StatusInternalServerError, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
response.WriteHeader(http.StatusCreated)
|
|
||||||
_http.WriteEntity(response, entityPtr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (collection *CollectionInstance) update(response http.ResponseWriter, request *http.Request) {
|
|
||||||
entityPtr := reflect.New(collection.instanceType).Interface() //collection.instanceProviderPtr.NewInstance()
|
|
||||||
err := _http.ReadEntity(request, entityPtr)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
_http.WriteErrorResponse(response, http.StatusBadRequest, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
id := request.Form.Get(IDPathParameter)
|
|
||||||
err = collection.service.Find(&[]interface{}{}, map[string]interface{}{IDPathParameter: id})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if err == _http.ErrNotFound {
|
|
||||||
_http.WriteErrorResponse(response, http.StatusNotFound, fmt.Sprintf("%v with id %v not found", collection.name, id))
|
|
||||||
} else {
|
|
||||||
_http.WriteErrorResponse(response, http.StatusInternalServerError, err.Error())
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = collection.service.Update(entityPtr)
|
|
||||||
if err != nil {
|
|
||||||
_http.WriteErrorResponse(response, http.StatusInternalServerError, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
response.WriteHeader(http.StatusOK)
|
|
||||||
_http.WriteEntity(response, entityPtr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (collection *CollectionInstance) remove(response http.ResponseWriter, request *http.Request) {
|
|
||||||
id := request.Form.Get(IDPathParameter)
|
|
||||||
err := collection.service.Find(&[]interface{}{}, map[string]interface{}{IDPathParameter: id})
|
|
||||||
if err != nil {
|
|
||||||
if err == _http.ErrNotFound {
|
|
||||||
_http.WriteErrorResponse(response, http.StatusNotFound, fmt.Sprintf("%v with id %v not found", collection.name, id))
|
|
||||||
} else {
|
|
||||||
_http.WriteErrorResponse(response, http.StatusInternalServerError, err.Error())
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
entityPtr := reflect.New(collection.instanceType).Interface() //collection.instanceProviderPtr.NewInstance()
|
|
||||||
field := reflect.Indirect(reflect.ValueOf(entityPtr)).FieldByName(strings.ToUpper(IDPathParameter))
|
|
||||||
if !field.CanSet() {
|
|
||||||
_http.WriteErrorResponse(response, http.StatusInternalServerError, "entity does not have "+IDPathParameter+" field or field is not setable")
|
|
||||||
}
|
|
||||||
parsedID, err := strconv.ParseInt(id, 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
_http.WriteErrorResponse(response, http.StatusInternalServerError, err.Error())
|
|
||||||
|
|
||||||
}
|
|
||||||
field.SetInt(parsedID)
|
|
||||||
|
|
||||||
err = collection.service.Delete(entityPtr)
|
|
||||||
if err != nil {
|
|
||||||
_http.WriteErrorResponse(response, http.StatusInternalServerError, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
response.WriteHeader(http.StatusNoContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (collection *CollectionInstance) find(response http.ResponseWriter, request *http.Request) {
|
|
||||||
id := request.Form.Get(IDPathParameter)
|
|
||||||
arrv := reflect.New(reflect.SliceOf(reflect.PtrTo(collection.instanceType)))
|
|
||||||
arri := arrv.Interface()
|
|
||||||
err := collection.service.Find(arri, valuesToMap(request.URL.Query(), id))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if err == _http.ErrNotFound {
|
|
||||||
_http.WriteErrorResponse(response, http.StatusNotFound, fmt.Sprintf("%v with id %v not found", collection.name, id))
|
|
||||||
} else {
|
|
||||||
_http.WriteErrorResponse(response, http.StatusInternalServerError, err.Error())
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var results interface{}
|
|
||||||
|
|
||||||
if reflect.Indirect(arrv).Len() == 1 {
|
|
||||||
results = reflect.Indirect(arrv).Index(0).Interface()
|
|
||||||
fmt.Println(results)
|
|
||||||
} else {
|
|
||||||
results = &ResultSetResponse{
|
|
||||||
Metadata: Metadata{
|
|
||||||
ResultSet: ResultSetMetadata{
|
|
||||||
Count: reflect.Indirect(arrv).Len(),
|
|
||||||
//TODO: need to accomidate limit and offset here.
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Results: arri,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
response.WriteHeader(http.StatusOK)
|
|
||||||
_http.WriteEntity(response, results)
|
|
||||||
}
|
|
||||||
|
|
||||||
func valuesToMap(params map[string][]string, id string) map[string]interface{} {
|
|
||||||
m := make(map[string]interface{})
|
|
||||||
for key, val := range params {
|
|
||||||
if len(val) == 1 {
|
|
||||||
m[key] = val[0]
|
|
||||||
} else {
|
|
||||||
m[key] = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if id != "" {
|
|
||||||
m[IDPathParameter] = id
|
|
||||||
}
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
@ -1,294 +0,0 @@
|
|||||||
package rest_test
|
|
||||||
|
|
||||||
// import (
|
|
||||||
// "encoding/json"
|
|
||||||
// "fmt"
|
|
||||||
// "io/ioutil"
|
|
||||||
// "log"
|
|
||||||
// "net/http"
|
|
||||||
// "net/http/httptest"
|
|
||||||
// "path/filepath"
|
|
||||||
// "reflect"
|
|
||||||
// "runtime"
|
|
||||||
// "strings"
|
|
||||||
// "testing"
|
|
||||||
|
|
||||||
// // "github.com/jchenry/crud"
|
|
||||||
// keel_http "github.com/jchenry/http"
|
|
||||||
// keel_httptest "github.com/jchenry/http/httptest"
|
|
||||||
// // "github.com/jchenry/rest"
|
|
||||||
// )
|
|
||||||
|
|
||||||
// type TestObject struct {
|
|
||||||
// ID int64 `json:"id"`
|
|
||||||
// Name string `json:"name"`
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var service *crud.InMemoryCrudService
|
|
||||||
// var instanceType reflect.Type = reflect.TypeOf(TestObject{})
|
|
||||||
|
|
||||||
// func Setup() {
|
|
||||||
// if service == nil {
|
|
||||||
// service = crud.NewInMemoryCrudService()
|
|
||||||
// }
|
|
||||||
// log.SetOutput(ioutil.Discard)
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // func TestRESTCollectionTestSuite(t *testing.T) {
|
|
||||||
// // rsuite := new(RESTCollectionTestSuite)
|
|
||||||
// // rservice = db.NewInMemoryCrudService()
|
|
||||||
// // rinstanceType = reflect.TypeOf(TestObject{})
|
|
||||||
// // log.SetOutput(ioutil.Discard)
|
|
||||||
// // Run(t, rsuite)
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// func TestCollectionCreate(t *testing.T) {
|
|
||||||
// Setup()
|
|
||||||
// to := new(TestObject)
|
|
||||||
// to.Name = "foo"
|
|
||||||
|
|
||||||
// container := createCollectionContainer(instanceType, service)
|
|
||||||
|
|
||||||
// requestJSON, err := json.Marshal(TestObject{Name: "Foo"})
|
|
||||||
// if err != nil {
|
|
||||||
// Fail(t, "unable to json body")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// request, err := http.NewRequest("POST", "/testobjects", strings.NewReader(string(requestJSON)))
|
|
||||||
// request.Header.Add("Content-Type", keel_http.MimeJSON)
|
|
||||||
// if err != nil {
|
|
||||||
// Fail(t, "unable to create request")
|
|
||||||
// }
|
|
||||||
// response := httptest.NewRecorder()
|
|
||||||
|
|
||||||
// container.ServeHTTP(response, request)
|
|
||||||
// keel_httptest.ValidateResponse(t, response, 201, "{\n \"id\": 1,\n \"name\": \"Foo\"\n }")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestCollectionCreateBadRequest(t *testing.T) {
|
|
||||||
// Setup()
|
|
||||||
// to := new(TestObject)
|
|
||||||
// to.Name = "foo"
|
|
||||||
|
|
||||||
// container := createCollectionContainer(instanceType, service)
|
|
||||||
|
|
||||||
// request, err := http.NewRequest("POST", "/testobjects", strings.NewReader(string("{malformedjson}")))
|
|
||||||
// request.Header.Add("Content-Type", keel_http.MimeJSON)
|
|
||||||
// if err != nil {
|
|
||||||
// Fail(t, "unable to create request")
|
|
||||||
// }
|
|
||||||
// response := httptest.NewRecorder()
|
|
||||||
|
|
||||||
// container.ServeHTTP(response, request)
|
|
||||||
|
|
||||||
// keel_httptest.ValidateResponse(t, response, 400, "{\n \"Status\": 400,\n \"DeveloperMessage\": \"invalid character 'm' looking for beginning of object key string\"\n }")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestCollectionCreateInternalError(t *testing.T) {
|
|
||||||
// Setup()
|
|
||||||
// to := new(TestObject)
|
|
||||||
// to.Name = "foo"
|
|
||||||
|
|
||||||
// container := createCollectionContainer(instanceType, NewAllFailingCrudService())
|
|
||||||
|
|
||||||
// requestJSON, err := json.Marshal(TestObject{Name: "Foo"})
|
|
||||||
// if err != nil {
|
|
||||||
// Fail(t, "unable to json body")
|
|
||||||
// }
|
|
||||||
// request, err := http.NewRequest("POST", "/testobjects", strings.NewReader(string(requestJSON)))
|
|
||||||
// request.Header.Add("Content-Type", keel_http.MimeJSON)
|
|
||||||
// if err != nil {
|
|
||||||
// Fail(t, "unable to create request")
|
|
||||||
// }
|
|
||||||
// response := httptest.NewRecorder()
|
|
||||||
|
|
||||||
// container.ServeHTTP(response, request)
|
|
||||||
|
|
||||||
// keel_httptest.ValidateResponse(t, response, 500, "{\n \"Status\": 500,\n \"DeveloperMessage\": \"unable to create\"\n }")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestCollectionUpdate(t *testing.T) {
|
|
||||||
// Setup()
|
|
||||||
// to := new(TestObject)
|
|
||||||
// to.Name = "foo"
|
|
||||||
// to.ID = 1
|
|
||||||
|
|
||||||
// container := createCollectionContainer(instanceType, service)
|
|
||||||
|
|
||||||
// requestJSON := keel_httptest.GetJSONReader(t, to)
|
|
||||||
|
|
||||||
// request, response := keel_httptest.GetRequestAndResponse(t, "PUT", "/testobjects/1", requestJSON)
|
|
||||||
|
|
||||||
// container.ServeHTTP(response, request)
|
|
||||||
// keel_httptest.ValidateResponse(t, response, 200, "{\n \"id\": 1,\n \"name\": \"foo\"\n }")
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestCollectionUpdateBadRequest(t *testing.T) {
|
|
||||||
// Setup()
|
|
||||||
// to := new(TestObject)
|
|
||||||
// to.Name = "foo"
|
|
||||||
// to.ID = 1
|
|
||||||
|
|
||||||
// container := createCollectionContainer(instanceType, service)
|
|
||||||
|
|
||||||
// request, response := keel_httptest.GetRequestAndResponse(t, "PUT", "/testobjects/1", strings.NewReader(string("{malformedjson}")))
|
|
||||||
|
|
||||||
// container.ServeHTTP(response, request)
|
|
||||||
// keel_httptest.ValidateResponse(t, response, 400, "{\n \"Status\": 400,\n \"DeveloperMessage\": \"invalid character 'm' looking for beginning of object key string\"\n }")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestCollectionUpdateBadEntityID(t *testing.T) {
|
|
||||||
// Setup()
|
|
||||||
// to := new(TestObject)
|
|
||||||
// to.Name = "foo"
|
|
||||||
// to.ID = 42
|
|
||||||
|
|
||||||
// container := createCollectionContainer(instanceType, service)
|
|
||||||
// requestJSON := keel_httptest.GetJSONReader(t, to)
|
|
||||||
|
|
||||||
// request, response := keel_httptest.GetRequestAndResponse(t, "PUT", "/testobjects/42", requestJSON)
|
|
||||||
|
|
||||||
// container.ServeHTTP(response, request)
|
|
||||||
// keel_httptest.ValidateResponse(t, response, 404, "{\n \"Status\": 404,\n \"DeveloperMessage\": \"testobject with id 42 not found\"\n }")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestCollectionUpdateInternalErrorOnFind(t *testing.T) {
|
|
||||||
// Setup()
|
|
||||||
// to := new(TestObject)
|
|
||||||
// to.Name = "foo"
|
|
||||||
// to.ID = 1
|
|
||||||
|
|
||||||
// container := createCollectionContainer(instanceType, NewAllFailingCrudService())
|
|
||||||
// requestJSON := keel_httptest.GetJSONReader(t, to)
|
|
||||||
// request, response := keel_httptest.GetRequestAndResponse(t, "PUT", "/testobjects/1", requestJSON)
|
|
||||||
// container.ServeHTTP(response, request)
|
|
||||||
// keel_httptest.ValidateResponse(t, response, 500, "{\n \"Status\": 500,\n \"DeveloperMessage\": \"unable to find\"\n }")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestCollectionUpdateInternalErrorOnServiceUpdate(t *testing.T) {
|
|
||||||
// Setup()
|
|
||||||
// to := new(TestObject)
|
|
||||||
// to.Name = "foo"
|
|
||||||
// to.ID = 1
|
|
||||||
|
|
||||||
// container := createCollectionContainer(instanceType, NewFailingCrudService(service, false, false, true, false))
|
|
||||||
// requestJSON := keel_httptest.GetJSONReader(t, to)
|
|
||||||
// request, response := keel_httptest.GetRequestAndResponse(t, "PUT", "/testobjects/1", requestJSON)
|
|
||||||
// container.ServeHTTP(response, request)
|
|
||||||
// keel_httptest.ValidateResponse(t, response, 500, "{\n \"Status\": 500,\n \"DeveloperMessage\": \"unable to update\"\n }")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestDelete(t *testing.T) {
|
|
||||||
// Setup()
|
|
||||||
// to := new(TestObject)
|
|
||||||
// to.Name = "deleteObject"
|
|
||||||
|
|
||||||
// _, err := service.Create(to)
|
|
||||||
// if err != nil {
|
|
||||||
// Fail(t, "unable to create deleteObject")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// container := createCollectionContainer(instanceType, service)
|
|
||||||
// uri := fmt.Sprintf("/testobjects/%d", int(to.ID))
|
|
||||||
// request, response := keel_httptest.GetRequestAndResponse(t, "DELETE", uri, nil)
|
|
||||||
// container.ServeHTTP(response, request)
|
|
||||||
// keel_httptest.ValidateResponse(t, response, 204, "")
|
|
||||||
|
|
||||||
// request, response = keel_httptest.GetRequestAndResponse(t, "DELETE", uri, nil)
|
|
||||||
// container.ServeHTTP(response, request)
|
|
||||||
// keel_httptest.ValidateResponse(t, response, 404, "{\n \"Status\": 404,\n \"DeveloperMessage\": \"testobject with id 2 not found\"\n }")
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestCollectionDeleteInternalErrorOnFind(t *testing.T) {
|
|
||||||
// Setup()
|
|
||||||
// to := new(TestObject)
|
|
||||||
// to.Name = "foo"
|
|
||||||
// to.ID = 1
|
|
||||||
|
|
||||||
// container := createCollectionContainer(instanceType, NewAllFailingCrudService())
|
|
||||||
// requestJSON := keel_httptest.GetJSONReader(t, to)
|
|
||||||
// request, response := keel_httptest.GetRequestAndResponse(t, "DELETE", "/testobjects/1", requestJSON)
|
|
||||||
// container.ServeHTTP(response, request)
|
|
||||||
// keel_httptest.ValidateResponse(t, response, 500, "{\n \"Status\": 500,\n \"DeveloperMessage\": \"unable to find\"\n }")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestCollectionDeleteInternalErrorOnServiceDelete(t *testing.T) {
|
|
||||||
// Setup()
|
|
||||||
// to := new(TestObject)
|
|
||||||
// to.Name = "foo"
|
|
||||||
// to.ID = 1
|
|
||||||
|
|
||||||
// container := createCollectionContainer(instanceType, NewFailingCrudService(service, false, false, false, true))
|
|
||||||
// requestJSON := keel_httptest.GetJSONReader(t, to)
|
|
||||||
// request, response := keel_httptest.GetRequestAndResponse(t, "DELETE", "/testobjects/1", requestJSON)
|
|
||||||
// container.ServeHTTP(response, request)
|
|
||||||
// keel_httptest.ValidateResponse(t, response, 500, "{\n \"Status\": 500,\n \"DeveloperMessage\": \"unable to delete\"\n }")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestCollectionFindSingleItem(t *testing.T) {
|
|
||||||
// Setup()
|
|
||||||
// container := createCollectionContainer(instanceType, service)
|
|
||||||
// uri := fmt.Sprintf("/testobjects/%d", 1)
|
|
||||||
// request, response := keel_httptest.GetRequestAndResponse(t, "GET", uri, nil)
|
|
||||||
// container.ServeHTTP(response, request)
|
|
||||||
// keel_httptest.ValidateResponse(t, response, 200, "{\n \"id\": 1,\n \"name\": \"foo\"\n }")
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //TODO we really should support thie in InMemoryCrudService for code coverage purposes
|
|
||||||
// // func TestFindOnQuery(t *testing.T) {
|
|
||||||
// // container := createCollectionContainer(TestObject{}, service)
|
|
||||||
// // uri := "/testobjects?name=foo"
|
|
||||||
// // request, response := keel_httptest.GetRequestAndResponse(t, "GET", uri, nil)
|
|
||||||
// // container.ServeHTTP(response, request)
|
|
||||||
// // keel_httptest.ValidateResponse(t, response, 200, "{\n \"id\": 1,\n \"name\": \"Foo\"\n }")
|
|
||||||
// //
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// func TestCollectionFindInternalFailure(t *testing.T) {
|
|
||||||
// Setup()
|
|
||||||
// container := createCollectionContainer(instanceType, NewFailingCrudService(service, false, true, false, false))
|
|
||||||
// uri := fmt.Sprintf("/testobjects/%d", 1)
|
|
||||||
// request, response := keel_httptest.GetRequestAndResponse(t, "GET", uri, nil)
|
|
||||||
// container.ServeHTTP(response, request)
|
|
||||||
// keel_httptest.ValidateResponse(t, response, 500, "{\n \"Status\": 500,\n \"DeveloperMessage\": \"unable to find\"\n }")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func createCollectionContainer(entity reflect.Type, service crud.CrudService) *rest.Server {
|
|
||||||
// s := rest.NewServer().
|
|
||||||
// Service("", rest.NewCollection(entity, service))
|
|
||||||
// return s
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func Fail(tb testing.TB, msg string) {
|
|
||||||
// Assert(tb, false, msg)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Assert fails the test if the condition is false.
|
|
||||||
// func Assert(tb testing.TB, condition bool, msg string, v ...interface{}) {
|
|
||||||
// if !condition {
|
|
||||||
// _, file, line, _ := runtime.Caller(1)
|
|
||||||
// fmt.Printf("\033[31m%s:%d: "+msg+"\033[39m\n\n", append([]interface{}{filepath.Base(file), line}, v...)...)
|
|
||||||
// tb.FailNow()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func NewAllFailingCrudService() crud.CrudService {
|
|
||||||
// return failingCrudService{}
|
|
||||||
// }
|
|
||||||
|
|
||||||
// type failingCrudService struct{}
|
|
||||||
|
|
||||||
// func (a *failingCrudService) Find(entityArrPtr interface{}, params map[string]interface{}) (err error) {
|
|
||||||
// return crud.ErrNotFound
|
|
||||||
// }
|
|
||||||
// func (a *failingCrudService) Create(entityPtr interface{}) (id interface{}, err error) {
|
|
||||||
// return nil, crud.ErrBadIDType
|
|
||||||
// }
|
|
||||||
// func (a *failingCrudService) Update(entityPtr interface{}) (id interface{}, err error) {
|
|
||||||
// return nil, crud.ErrNotFound
|
|
||||||
// }
|
|
||||||
// func (a *failingCrudService) Delete(entityPtr interface{}) error { return crud.ErrNotFound }
|
|
@ -1,19 +0,0 @@
|
|||||||
package rest
|
|
||||||
|
|
||||||
//ResultSetMetadata -
|
|
||||||
type ResultSetMetadata struct {
|
|
||||||
Count int `json:"count"`
|
|
||||||
Offset int `json:"offset"`
|
|
||||||
Limit int `json:"limit"`
|
|
||||||
}
|
|
||||||
|
|
||||||
//Metadata -
|
|
||||||
type Metadata struct {
|
|
||||||
ResultSet ResultSetMetadata `json:"resultset"`
|
|
||||||
}
|
|
||||||
|
|
||||||
//ResultSetResponse -
|
|
||||||
type ResultSetResponse struct {
|
|
||||||
Metadata Metadata `json:"metadata"`
|
|
||||||
Results interface{} `json:"results"`
|
|
||||||
}
|
|
118
internal/rss/model.go
Normal file
118
internal/rss/model.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package rss
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Feed is an RSS Feed
|
||||||
|
type Feed struct {
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
Link string `json:"link,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Language string `json:"language,omitempty"`
|
||||||
|
Copyright string `json:"copyright,omitempty"`
|
||||||
|
ManagingEditor string `json:"managingEditor,omitempty"`
|
||||||
|
WebMaster string `json:"webMaster,omitempty"`
|
||||||
|
PubDate string `json:"pubDate,omitempty"`
|
||||||
|
PubDateParsed *time.Time `json:"pubDateParsed,omitempty"`
|
||||||
|
LastBuildDate string `json:"lastBuildDate,omitempty"`
|
||||||
|
LastBuildDateParsed *time.Time `json:"lastBuildDateParsed,omitempty"`
|
||||||
|
Categories []*Category `json:"categories,omitempty"`
|
||||||
|
Generator string `json:"generator,omitempty"`
|
||||||
|
Docs string `json:"docs,omitempty"`
|
||||||
|
TTL string `json:"ttl,omitempty"`
|
||||||
|
Image *Image `json:"image,omitempty"`
|
||||||
|
Rating string `json:"rating,omitempty"`
|
||||||
|
SkipHours []string `json:"skipHours,omitempty"`
|
||||||
|
SkipDays []string `json:"skipDays,omitempty"`
|
||||||
|
Cloud *Cloud `json:"cloud,omitempty"`
|
||||||
|
TextInput *TextInput `json:"textInput,omitempty"`
|
||||||
|
// DublinCoreExt *ext.DublinCoreExtension `json:"dcExt,omitempty"`
|
||||||
|
// ITunesExt *ext.ITunesFeedExtension `json:"itunesExt,omitempty"`
|
||||||
|
// Extensions ext.Extensions `json:"extensions,omitempty"`
|
||||||
|
Items []*Item `json:"items"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Feed) String() string {
|
||||||
|
json, _ := json.MarshalIndent(f, "", " ")
|
||||||
|
return string(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item is an RSS Item
|
||||||
|
type Item struct {
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
Link string `json:"link,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Content string `json:"content,omitempty"`
|
||||||
|
Author string `json:"author,omitempty"`
|
||||||
|
Categories []*Category `json:"categories,omitempty"`
|
||||||
|
Comments string `json:"comments,omitempty"`
|
||||||
|
Enclosure *Enclosure `json:"enclosure,omitempty"`
|
||||||
|
GUID *GUID `json:"guid,omitempty"`
|
||||||
|
PubDate string `json:"pubDate,omitempty"`
|
||||||
|
PubDateParsed *time.Time `json:"pubDateParsed,omitempty"`
|
||||||
|
Source *Source `json:"source,omitempty"`
|
||||||
|
// DublinCoreExt *ext.DublinCoreExtension `json:"dcExt,omitempty"`
|
||||||
|
// ITunesExt *ext.ITunesItemExtension `json:"itunesExt,omitempty"`
|
||||||
|
// Extensions ext.Extensions `json:"extensions,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image is an image that represents the feed
|
||||||
|
type Image struct {
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
Link string `json:"link,omitempty"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
Width string `json:"width,omitempty"`
|
||||||
|
Height string `json:"height,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enclosure is a media object that is attached to
|
||||||
|
// the item
|
||||||
|
type Enclosure struct {
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
Length string `json:"length,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GUID is a unique identifier for an item
|
||||||
|
type GUID struct {
|
||||||
|
Value string `json:"value,omitempty"`
|
||||||
|
IsPermalink string `json:"isPermalink,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source contains feed information for another
|
||||||
|
// feed if a given item came from that feed
|
||||||
|
type Source struct {
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Category is category metadata for Feeds and Entries
|
||||||
|
type Category struct {
|
||||||
|
Domain string `json:"domain,omitempty"`
|
||||||
|
Value string `json:"value,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextInput specifies a text input box that
|
||||||
|
// can be displayed with the channel
|
||||||
|
type TextInput struct {
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Link string `json:"link,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cloud allows processes to register with a
|
||||||
|
// cloud to be notified of updates to the channel,
|
||||||
|
// implementing a lightweight publish-subscribe protocol
|
||||||
|
// for RSS feeds
|
||||||
|
type Cloud struct {
|
||||||
|
Domain string `json:"domain,omitempty"`
|
||||||
|
Port string `json:"port,omitempty"`
|
||||||
|
Path string `json:"path,omitempty"`
|
||||||
|
RegisterProcedure string `json:"registerProcedure,omitempty"`
|
||||||
|
Protocol string `json:"protocol,omitempty"`
|
||||||
|
}
|
21
internal/rss/parser.go
Normal file
21
internal/rss/parser.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package rss
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"golang.org/x/net/html"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Parse(r io.Reader) *Feed {
|
||||||
|
|
||||||
|
// z := html.NewTokenizer(r)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFeed(z html.Tokenizer) *Feed {
|
||||||
|
z.Next()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseVersion(z html.Tokenizer) {}
|
@ -32,6 +32,5 @@ func MutliHandler(h map[string]http.Handler) (http.HandlerFunc, error) {
|
|||||||
} else {
|
} else {
|
||||||
NotFoundHandler.ServeHTTP(w, r)
|
NotFoundHandler.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
107
rest/collection.go
Executable file
107
rest/collection.go
Executable file
@ -0,0 +1,107 @@
|
|||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/jchenry/x/encoding"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CollectionStore interface {
|
||||||
|
All(params url.Values) (interface{}, error)
|
||||||
|
Get(id string) (interface{}, error)
|
||||||
|
Delete(id string) error
|
||||||
|
Update(e interface{}) error
|
||||||
|
New(e interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example: Collection(p, c, JSONEncoder, json.Decode(func()interface{}{return &foo{}}))
|
||||||
|
|
||||||
|
// type Decoder func(io.Reader) (interface{}, error)
|
||||||
|
|
||||||
|
func Collection(pool *sync.Pool, store CollectionStore, encode EntityEncoder, decode encoding.Decoder) http.HandlerFunc {
|
||||||
|
return EntityHandler(
|
||||||
|
collectionGet(store, encode),
|
||||||
|
collectionPost(store, encode, decode, pool),
|
||||||
|
collectionPut(store, encode, decode, pool),
|
||||||
|
collectionDelete(store, encode),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionGet(store CollectionStore, encode EntityEncoder) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) { // GET
|
||||||
|
if id := filepath.Base(r.URL.Path); id != "" {
|
||||||
|
if e, err := store.Get(id); err == nil { // handle individual entity
|
||||||
|
encode(w, e)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
encode(w, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if params, err := url.ParseQuery(r.URL.RawQuery); err == nil {
|
||||||
|
if e, err := store.All(params); err == nil { // handle all entities
|
||||||
|
encode(w, e)
|
||||||
|
} else {
|
||||||
|
// TODO: we really should write a header here, but need to figure out what it should be
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// encode(w, err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionPost(store CollectionStore, encode EntityEncoder, decode encoding.Decoder, pool *sync.Pool) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) { // POST TODO
|
||||||
|
e := pool.New()
|
||||||
|
defer pool.Put(e)
|
||||||
|
if err := decode(r.Body, e); err == nil {
|
||||||
|
if err = store.New(e); err == nil {
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionPut(store CollectionStore, encode EntityEncoder, decode encoding.Decoder, pool *sync.Pool) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) { // PUT TODO
|
||||||
|
e := pool.New()
|
||||||
|
defer pool.Put(e)
|
||||||
|
if err := decode(r.Body, e); err == nil {
|
||||||
|
if err = store.Update(e); err == nil {
|
||||||
|
w.WriteHeader(http.StatusAccepted)
|
||||||
|
encode(w, e)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
encode(w, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
encode(w, err)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionDelete(store CollectionStore, encode EntityEncoder) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) { // DELETE TODO
|
||||||
|
if id := filepath.Base(r.URL.Path); id != "" {
|
||||||
|
if err := store.Delete(id); err == nil {
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
rest/entity_handler.go
Normal file
23
rest/entity_handler.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
gohttp "net/http"
|
||||||
|
|
||||||
|
"github.com/jchenry/x/net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EntityHandler returns a handler that provides restful verbs, following a CRUD model
|
||||||
|
func EntityHandler(
|
||||||
|
get gohttp.Handler,
|
||||||
|
post gohttp.Handler,
|
||||||
|
put gohttp.Handler,
|
||||||
|
delete gohttp.Handler,
|
||||||
|
) gohttp.HandlerFunc {
|
||||||
|
h, _ := http.MutliHandler(map[string]gohttp.Handler{
|
||||||
|
gohttp.MethodGet: get,
|
||||||
|
gohttp.MethodPost: post,
|
||||||
|
gohttp.MethodPut: put,
|
||||||
|
gohttp.MethodDelete: delete,
|
||||||
|
})
|
||||||
|
return h
|
||||||
|
}
|
32
rest/response_encoder.go
Normal file
32
rest/response_encoder.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/jchenry/x/encoding"
|
||||||
|
"github.com/jchenry/x/encoding/json"
|
||||||
|
"github.com/jchenry/x/encoding/xml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EntityEncoder func(w http.ResponseWriter, e interface{})
|
||||||
|
|
||||||
|
func JSONEncoder(w http.ResponseWriter, e interface{}) error {
|
||||||
|
return EntityResponseEncoder(w, "application/json", json.Encoder, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func XMLEncoder(w http.ResponseWriter, e interface{}) error {
|
||||||
|
return EntityResponseEncoder(w, "application/xml", xml.Encoder, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func EntityResponseEncoder(w http.ResponseWriter, contentType string, encoder encoding.Encoder, e interface{}) error {
|
||||||
|
w.Header().Set("content-type", contentType)
|
||||||
|
return encoder(w, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrorResponseEncoder(w http.ResponseWriter, contentType string, encoder encoding.Encoder, status int, err error) error {
|
||||||
|
w.WriteHeader(status)
|
||||||
|
return EntityResponseEncoder(w, contentType, encoder, map[string]interface{}{
|
||||||
|
"status": status,
|
||||||
|
"message": err.Error,
|
||||||
|
})
|
||||||
|
}
|
@ -1,5 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
docker run --rm -v "${PWD}:/local" openapitools/openapi-generator-cli generate \
|
|
||||||
-i $1 \ #https://raw.githubusercontent.com/openapitools/openapi-generator/master/modules/openapi-generator/src/test/resources/2_0/petstore.yaml \
|
|
||||||
-g go \
|
|
||||||
-o /local/out/go
|
|
@ -1,15 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# A port of plan9 'tel' program
|
|
||||||
|
|
||||||
for var in "$@"
|
|
||||||
do
|
|
||||||
if test -f "$HOME/.tel"; then
|
|
||||||
grep -i $1 $HOME/.tel
|
|
||||||
fi
|
|
||||||
|
|
||||||
grep -hi $1 /usr/lib/tel /usr/lib/areacodes
|
|
||||||
done
|
|
||||||
|
|
||||||
exit
|
|
||||||
|
|
||||||
|
|
@ -6,7 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jchenry/libs/arvelie"
|
"github.com/jchenry/x/time/arvelie"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFromDate(t *testing.T) {
|
func TestFromDate(t *testing.T) {
|
||||||
@ -49,5 +49,4 @@ func TestToDate(t *testing.T) {
|
|||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -6,7 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jchenry/libs/neralie"
|
"github.com/jchenry/x/time/neralie"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFromTime(t *testing.T) {
|
func TestFromTime(t *testing.T) {
|
Loading…
x
Reference in New Issue
Block a user