package reorg

This commit is contained in:
Colin Henry 2020-06-11 21:26:57 -07:00
parent b7b8f7a60b
commit f24e8c90df
64 changed files with 622 additions and 17 deletions

2
.gitignore vendored
View File

@ -2,4 +2,4 @@ cmd/sub-demo/sub-demo
cmd/auth-demo/auth-demo
.vscode/
**/.DS_Store
**/*.db

View File

@ -5,8 +5,8 @@ import (
"os"
"github.com/codegangsta/negroni"
"github.com/jchenry/jchenry/auth"
_http "github.com/jchenry/jchenry/http"
"github.com/jchenry/jchenry/internal/auth"
_http "github.com/jchenry/jchenry/internal/http"
)
func main() {

49
cmd/gcal2calendar/main.go Normal file
View File

@ -0,0 +1,49 @@
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")
}

64
cmd/jchsh/main.go Normal file
View File

@ -0,0 +1,64 @@
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()
}

View File

@ -4,7 +4,7 @@ import (
"fmt"
"time"
"github.com/jchenry/jchenry/neralie"
"github.com/jchenry/jchenry/pkg/neralie"
)
func main() {

View File

@ -5,9 +5,9 @@ import (
"os"
"github.com/codegangsta/negroni"
"github.com/jchenry/jchenry/auth"
_http "github.com/jchenry/jchenry/http"
"github.com/jchenry/jchenry/payments"
"github.com/jchenry/jchenry/internal/auth"
_http "github.com/jchenry/jchenry/internal/http"
"github.com/jchenry/jchenry/internal/payments"
)
func main() {

View File

@ -4,7 +4,7 @@ import (
"fmt"
"time"
"github.com/jchenry/jchenry/arvelie"
"github.com/jchenry/jchenry/pkg/arvelie"
)
func main() {

Binary file not shown.

View File

@ -15,5 +15,4 @@ func (s *server) routes() {
s.router.HandleFunc("/time", s.handleTime())
s.router.HandleFunc("/echo", s.handleEcho())
s.router.HandleFunc("/fortune", s.handleFortune())
}

View File

@ -1,14 +1,22 @@
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, time.Now().String())
io.WriteString(w, t.String())
io.WriteString(w, fmt.Sprintf("\n%s %s",
arvelie.FromDate(t),
neralie.FromTime(t)))
}
}

13
cmd/wiki/edit.go Normal file
View File

@ -0,0 +1,13 @@
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
}

18
cmd/wiki/http.go Normal file
View File

@ -0,0 +1,18 @@
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)
}
}

47
cmd/wiki/main.go Normal file
View File

@ -0,0 +1,47 @@
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)
}
}
}))))
}

18
cmd/wiki/os.go Normal file
View File

@ -0,0 +1,18 @@
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)
}

17
cmd/wiki/render.go Normal file
View File

@ -0,0 +1,17 @@
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
}

14
cmd/wiki/save.go Normal file
View File

@ -0,0 +1,14 @@
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
}

32
cmd/wiki/search.go Normal file
View File

@ -0,0 +1,32 @@
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
}

47
cmd/wiki/view.go Normal file
View File

@ -0,0 +1,47 @@
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)
}

6
go.mod
View File

@ -3,11 +3,16 @@ module github.com/jchenry/jchenry
go 1.13
require (
github.com/PuloV/ics-golang v0.0.0-20190808201353-a3394d3bcade
github.com/channelmeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61 // indirect
github.com/codegangsta/negroni v1.0.0
github.com/coreos/go-oidc v2.1.0+incompatible
github.com/gorilla/sessions v1.2.0
github.com/julienschmidt/httprouter v1.2.0
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/rsc/rsc v0.0.0-20180427141835-fc6202590229
github.com/russross/blackfriday/v2 v2.0.1
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
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad // indirect
@ -15,4 +20,5 @@ require (
gopkg.in/auth0.v1 v1.2.7
gopkg.in/square/go-jose.v2 v2.3.1 // indirect
rsc.io/dbstore v0.1.1
rsc.io/rsc v0.0.0-20180427141835-fc6202590229 // indirect
)

13
go.sum
View File

@ -1,6 +1,10 @@
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
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/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/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/codegangsta/negroni v1.0.0 h1:+aYywywx4bnKXWvoWtRfJ91vC59NbEhEY03sZjQhbVY=
github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0=
github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM=
@ -19,6 +23,13 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
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/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/rsc/rsc v0.0.0-20180427141835-fc6202590229 h1:s5M0EEh5JyTx0PrhLGlog+CegHIkmiCd07ht20coRtA=
github.com/rsc/rsc v0.0.0-20180427141835-fc6202590229/go.mod h1:TJRSe/n0/H37q9TsEwBtcOz32UX+UWqgapLwsXTV4jE=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
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/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 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -53,5 +64,7 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
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/rsc v0.0.0-20180427141835-fc6202590229 h1:6s5zUknxnRp4D3GlNb7uDzlcfFVq9G2ficO+k4Bcb6w=
rsc.io/rsc v0.0.0-20180427141835-fc6202590229/go.mod h1:nHU4RAWoD9u1Hr+vTW0mktVbANmwCPkTwT2xNpVs/70=
rsc.io/sqlite v0.5.0 h1:HG63YxeP0eALjqorwnJ9ENxUUOUR6NYJ4FHEKFJ7aVk=
rsc.io/sqlite v0.5.0/go.mod h1:fqHuveM9iIqMzjD0WiZIvKYMty/WqTo2bxE9+zC54WE=

View File

@ -4,7 +4,7 @@ import (
"net/http"
"github.com/codegangsta/negroni"
_http "github.com/jchenry/jchenry/http"
_http "github.com/jchenry/jchenry/internal/http"
"gopkg.in/auth0.v1/management"
)

View File

@ -3,7 +3,7 @@ package auth
import (
"net/http"
jchenry_http "github.com/jchenry/jchenry/http"
jchenry_http "github.com/jchenry/jchenry/internal/http"
)
func UserHandler(w http.ResponseWriter, r *http.Request) {

44
internal/crud/service.go Normal file
View File

@ -0,0 +1,44 @@
package crud
import (
"github.com/jchenry/jchenry/pkg/db"
)
type Service 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{}) (err error)
// Create returns the identifier for the newly accepted entity, or error
Create(entityPtr interface{}) (err error)
// Update returns the id of the newly updated entity, or error
Update(entityPtr interface{}) (err error)
// Delete returns whether the entity, specified by id, was successfully deleted
// or error
Delete(entityPtr interface{}) error
}
type Storage struct {
Actor db.Actor
FindOp func(entityArrPtr interface{}, params map[string]interface{}) db.Func
CreateOp func(entityPtr interface{}) db.Func
UpdateOp func(entityPtr interface{}) db.Func
DeleteOp func(entityPtr interface{}) db.Func
}
func (s *Storage) Find(entityArrPtr interface{}, params map[string]interface{}) (err error) {
s.Actor.ActionChan <- s.FindOp(entityArrPtr, params)
return nil
}
func (s *Storage) Create(entityPtr interface{}) (err error) {
s.Actor.ActionChan <- s.CreateOp(entityPtr)
return nil
}
func (s *Storage) Update(entityPtr interface{}) (err error) {
s.Actor.ActionChan <- s.UpdateOp(entityPtr)
return nil
}
func (s *Storage) Delete(entityPtr interface{}) error {
s.Actor.ActionChan <- s.DeleteOp(entityPtr)
return nil
}

18
internal/http/auth.go Normal file
View File

@ -0,0 +1,18 @@
package http
import (
"net/http"
"os"
)
func BasicAuth(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)
}
}

View File

@ -7,8 +7,8 @@ import (
)
type Middleware interface {
go_http.Handler
UseHandler(handler http.Handler)
ServeHTTP(w go_http.ResponseWriter, req *go_http.Request)
}
type Router interface {

28
internal/http/service.go Normal file
View File

@ -0,0 +1,28 @@
package http
// import "net/http"
// type Service interface {
// Register(s Server)
// }
// type Mux interface {
// Head(pattern string, handler http.Handler)
// Post(pattern string, handler http.Handler)
// Put(pattern string, handler http.Handler)
// Patch(pattern string, handler http.Handler)
// Delete(pattern string, handler http.Handler)
// Connect(pattern string, handler http.Handler)
// Options(pattern string, handler http.Handler)
// Trace(pattern string, handler http.Handler)
// }
// MethodGet = "GET"
// MethodHead = "HEAD"
// MethodPost = "POST"
// MethodPut = "PUT"
// MethodPatch = "PATCH" // RFC 5789
// MethodDelete = "DELETE"
// MethodConnect = "CONNECT"
// MethodOptions = "OPTIONS"
// MethodTrace = "TRACE"

View File

@ -21,3 +21,10 @@ func RenderTemplate(w http.ResponseWriter, tmpl string, data interface{}) {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func render(w http.ResponseWriter, tmpl string, data interface{}) (err error) {
if t, err := template.ParseFiles(tmpl); err == nil {
return t.Execute(w, data)
}
return err
}

View File

@ -3,7 +3,7 @@ package payments
import (
"net/http"
"github.com/jchenry/jchenry/auth"
"github.com/jchenry/jchenry/internal/auth"
)
func HasTenantAndSubscription(productID string) func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {

View File

@ -5,8 +5,8 @@ import (
"net/http"
"github.com/codegangsta/negroni"
"github.com/jchenry/jchenry/auth"
_http "github.com/jchenry/jchenry/http"
"github.com/jchenry/jchenry/internal/auth"
_http "github.com/jchenry/jchenry/internal/http"
"github.com/stripe/stripe-go"
"github.com/stripe/stripe-go/client"
"github.com/stripe/stripe-go/customer"

View File

@ -7,7 +7,7 @@ import (
"strconv"
"strings"
_http "github.com/jchenry/jchenry/http"
_http "github.com/jchenry/jchenry/internal/http"
)
const (

27
pkg/snowflake/README.md Normal file
View File

@ -0,0 +1,27 @@
# snowflake
A snowflake ID generator based on instagram and twitter's snowflake concept
## Install
```
go get github.com/jchenry/snowflake
```
## Usage
```
import "github.com/jchenry/snowflake"
func main(){
snowflake.Next()
}
```
## Contributing
PRs accepted.
## License
MIT © Colin Henry

91
pkg/snowflake/snowflake.go Executable file
View File

@ -0,0 +1,91 @@
package snowflake
import (
"fmt"
"hash/fnv"
"math"
"net"
"sync"
"time"
)
const (
totalBits = 64
epochBits = 32
nodeIDBits = 10
sequenceBits = 12
// Custom Epoch (January 1, 2015 Midnight UTC = 2015-01-01T00:00:00Z)
customEpoch uint64 = 1420070400000
)
var maxNodeID uint64
var maxSequence uint64
var nodeID uint64
var lastTimestamp uint64 = 0
var sequence uint64
func init() {
maxNodeID = uint64(math.Pow(2, nodeIDBits) - 1)
maxSequence = uint64(math.Pow(2, sequenceBits) - 1)
nodeID = generateNodeID()
}
func generateNodeID() uint64 {
var nodeID uint64
if interfaces, err := net.Interfaces(); err == nil {
h := fnv.New32a()
for _, i := range interfaces {
h.Write(i.HardwareAddr)
}
nodeID = uint64(h.Sum32())
} else {
panic("interfaces not available")
}
nodeID = nodeID & maxNodeID
return nodeID
}
var timestampMutex sync.Mutex
var sequenceMutex sync.Mutex
// Next returns the next logical snowflake
func Next() uint64 {
timestampMutex.Lock()
currentTimestamp := ts()
timestampMutex.Unlock()
sequenceMutex.Lock()
if currentTimestamp == lastTimestamp {
sequence = (sequence + 1) & maxSequence
if sequence == 0 {
// Sequence Exhausted, wait till next millisecond.
currentTimestamp = waitNextMillis(currentTimestamp)
}
} else {
sequence = 0
}
sequenceMutex.Unlock()
lastTimestamp = currentTimestamp
id := currentTimestamp << (totalBits - epochBits)
fmt.Printf("%b\n", id)
id |= (nodeID << (totalBits - epochBits - nodeIDBits))
fmt.Printf("%b\n", id)
id |= sequence
fmt.Printf("%b\n", id)
return id
}
func ts() uint64 {
return uint64(time.Now().UnixNano()/1000000) - customEpoch
}
func waitNextMillis(currentTimestamp uint64) uint64 {
for currentTimestamp == lastTimestamp {
currentTimestamp = ts()
}
return currentTimestamp
}

25
pkg/snowflake/snowflake_test.go Executable file
View File

@ -0,0 +1,25 @@
package snowflake
import (
"fmt"
"testing"
)
func TestNext(t *testing.T) {
fmt.Printf("node id: %b\n", generateNodeID())
fmt.Printf("timestamp: %b\n", ts())
fmt.Printf("full token: %b\n", Next())
// t.Fail()
}
func BenchmarkNext(b *testing.B) {
for n := 0; n < b.N; n++ {
Next()
}
}
func BenchmarkNextParallel(b *testing.B) {
for n := 0; n < b.N; n++ {
go Next()
}
}

5
scripts/bin/openapigen.bash Executable file
View File

@ -0,0 +1,5 @@
#!/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

15
scripts/bin/tel.bash Executable file
View File

@ -0,0 +1,15 @@
#!/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