<commit_before>package jwt

//go:generate stringer -type Algorithm -output alg_string.go alg.go

import (
	"crypto"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rsa"
	"errors"
	"fmt"

	"github.com/knq/pemutil"
)

// Algorithm is the type for signing algorithms implemented in this package.
type Algorithm uint

// Signer is the shared interface for an Algorithm's encoding, decoding,
// signing, and verify to handle the crypto primitives and lower-level API
// calls.
type Signer interface {
	// Sign creates a signature for buf, returning it as a URL-safe base64
	// encoded byte slice.
	Sign(buf []byte) ([]byte, error)

	// Verify creates a signature for buf, comparing it against the URL-safe
	// base64 encoded sig. If the sig is invalid, then ErrInvalidSignature will
	// be returned.
	Verify(buf, sig []byte) ([]byte, error)

	// Encode encodes obj as a token.
	Encode(obj interface{}) ([]byte, error)

	// Decode decodes a serialized token, verifying the signature, storing the
	// decoded data from the token in obj.
	Decode(buf []byte, obj interface{}) error
}

// PEM is a wrapper that assists with loading PEM-encoded crypto primitives
// (ie, rsa.PrivateKey, ecdsa.PrivateKey, etc).
type PEM pemutil.PEM

const (
	// NONE provides a JWT signing method for NONE.
	//
	// NOTE: This is not implemented for security reasons.
	NONE Algorithm = iota

	// HS256 provides a JWT signing method for HMAC using SHA-256.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.2
	HS256

	// HS384 provides a JWT signing method for HMAC using SHA-384.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.2
	HS384

	// HS512 provides a JWT signing method for HMAC using SHA-512.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.2
	HS512

	// RS256 provides a JWT signing method for RSASSA-PKCS1-V1_5 using SHA-256.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.3
	RS256

	// RS384 provides a JWT signing method for RSASSA-PKCS1-V1_5 using SHA-384.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.3
	RS384

	// RS512 provides a JWT signing method for RSASSA-PKCS1-V1_5 using SHA-512.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.3
	RS512

	// ES256 provides a JWT signing method for ECDSA using P-256 and SHA-256.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.4
	ES256

	// ES384 provides a JWT signing method for ECDSA using P-384 and SHA-384.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.4
	ES384

	// ES512 provides a JWT signing method for ECDSA using P-521 and SHA-512.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.4
	ES512

	// PS256 provides a JWT signing method for RSASSA-PSS using SHA-256 and
	// MGF1 mask generation function with SHA-256.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.5
	PS256

	// PS384 provides a JWT signing method for RSASSA-PSS using SHA-384 hash
	// algorithm and MGF1 mask generation function with SHA-384.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.5
	PS384

	// PS512 provides a JWT signing method for RSASSA-PSS using SHA-512 hash
	// algorithm and MGF1 mask generation function with SHA-512.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.5
	PS512
)

// algMap is a map of algorithm implementations to its Algorithm.
var algMap = map[Algorithm]struct {
	NewFunc func(pemutil.Store, crypto.Hash) (Signer, error)
	Hash    crypto.Hash
}{
	// none
	NONE: {func(pemutil.Store, crypto.Hash) (Signer, error) {
		return nil, errors.New("jwt algorithm none is not implemented")
	}, crypto.SHA256},

	// HS256 is HMAC + SHA-256
	HS256: {NewHMACSigner(HS256), crypto.SHA256},

	// HS384 is HMAC + SHA-384
	HS384: {NewHMACSigner(HS384), crypto.SHA384},

	// HS512 is HMAC + SHA-512
	HS512: {NewHMACSigner(HS512), crypto.SHA512},

	// RS256 is RSASSA-PKCS1-V1_5 + SHA-256
	RS256: {NewRSASigner(RS256, PKCS1v15RSAMethod), crypto.SHA256},

	// RS384 is RSASSA-PKCS1-V1_5 + SHA-384
	RS384: {NewRSASigner(RS384, PKCS1v15RSAMethod), crypto.SHA384},

	// RS512 is RSASSA-PKCS1-V1_5 + SHA-512
	RS512: {NewRSASigner(RS512, PKCS1v15RSAMethod), crypto.SHA512},

	// ES256 is ECDSA P-256 + SHA-256
	ES256: {NewEllipticSigner(ES256, elliptic.P256()), crypto.SHA256},

	// ES384 is ECDSA P-384 + SHA-384
	ES384: {NewEllipticSigner(ES384, elliptic.P384()), crypto.SHA384},

	// ES512 is ECDSA P-521 + SHA-512
	ES512: {NewEllipticSigner(ES512, elliptic.P521()), crypto.SHA512},

	// PS256 is RSASSA-PSS + SHA-256
	PS256: {NewRSASigner(PS256, PSSRSAMethod), crypto.SHA256},

	// PS384 is RSASSA-PSS + SHA-384
	PS384: {NewRSASigner(PS384, PSSRSAMethod), crypto.SHA384},

	// PS512 is RSASSA-PSS + SHA-512
	PS512: {NewRSASigner(PS512, PSSRSAMethod), crypto.SHA512},
}

// New creates a Signer using the supplied keyset.
//
// The keyset can be of type PEM, pemutil.PEM, pemutil.Store,
// *rsa.{PrivateKey,PublicKey}, *ecdsa.{PrivateKey,PublicKey}, or []byte.
//
// PLEASE NOTE: if a calling package *DOES NOT* provide a private key, tokens
// cannot be Encode'd. Similarly, if no public key is provided, tokens *CANNOT*
// be Decode'd (ie, verified). Additionally, as this is a naive implementation,
// no attempt is made to generate or derive a public key from a private key.
// Therefore, please pass *BOTH* a public *AND* private key (if the Algorithm
// calls for it), wrapped by a pemutil.Store or as PEM/pemutil.PEM in order to
// Encode *AND* Decode JWTs.
func (alg Algorithm) New(keyset interface{}) (Signer, error) {
	var err error

	a := algMap[alg]

	// check hash
	if !a.Hash.Available() {
		return nil, fmt.Errorf("%s.New: crypto hash unavailable", alg)
	}

	var store pemutil.Store

	// load the data
	switch p := keyset.(type) {
	case pemutil.Store:
		store = p

	// pem data
	case pemutil.PEM:
		store, err = loadKeysFromPEM(p)
		if err != nil {
			return nil, err
		}
	case PEM:
		store, err = loadKeysFromPEM(pemutil.PEM(p))
		if err != nil {
			return nil, err
		}

	// raw key
	case []byte:
		store = pemutil.Store{pemutil.PrivateKey: p}

	// rsa keys
	case *rsa.PrivateKey:
		store = pemutil.Store{pemutil.RSAPrivateKey: p}
	case *rsa.PublicKey:
		store = pemutil.Store{pemutil.PublicKey: p}

	// ecc keys
	case *ecdsa.PrivateKey:
		store = pemutil.Store{pemutil.ECPrivateKey: p}
	case *ecdsa.PublicKey:
		store = pemutil.Store{pemutil.PublicKey: p}

	default:
		return nil, fmt.Errorf("%s.New: unrecognized keyset type", alg)
	}

	return a.NewFunc(store, a.Hash)
}

// Header builds the JWT header for the algorithm.
func (alg Algorithm) Header() Header {
	return Header{
		Type:      "JWT",
		Algorithm: alg,
	}
}

// Encode encodes a JWT using the Algorithm and Signer.
func (alg Algorithm) Encode(signer Signer, obj interface{}) ([]byte, error) {
	return Encode(alg, signer, obj)
}

// Decode verifies the signature of a JWT against the Algorithm, decoding any
// data in buf to obj.
func (alg Algorithm) Decode(signer Signer, buf []byte, obj interface{}) error {
	return Decode(alg, signer, buf, obj)
}

// MarshalText marshals Algorithm into a standard string.
func (alg Algorithm) MarshalText() ([]byte, error) {
	return []byte(alg.String()), nil
}

// UnmarshalText unmarshals a string into the corresponding Algorithm.
func (alg *Algorithm) UnmarshalText(buf []byte) error {
	switch string(buf) {
	// hmac
	case "HS256":
		*alg = HS256
	case "HS384":
		*alg = HS384
	case "HS512":
		*alg = HS512

	// rsa-pkcs1v15
	case "RS256":
		*alg = RS256
	case "RS384":
		*alg = RS384
	case "RS512":
		*alg = RS512

	// ecc
	case "ES256":
		*alg = ES256
	case "ES384":
		*alg = ES384
	case "ES512":
		*alg = ES512

	// rsa-pss
	case "PS256":
		*alg = PS256
	case "PS384":
		*alg = PS384
	case "PS512":
		*alg = PS512

	// errors
	case "none":
		return errors.New("algorithm none not supported")
	default:
		return errors.New("algorithm not supported")
	}

	return nil
}
<commit_msg>Adding public key generation for passed PEM data<commit_after>package jwt

//go:generate stringer -type Algorithm -output alg_string.go alg.go

import (
	"crypto"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rsa"
	"errors"
	"fmt"

	"github.com/knq/pemutil"
)

// Algorithm is the type for signing algorithms implemented in this package.
type Algorithm uint

// Signer is the shared interface for an Algorithm's encoding, decoding,
// signing, and verify to handle the crypto primitives and lower-level API
// calls.
type Signer interface {
	// Sign creates a signature for buf, returning it as a URL-safe base64
	// encoded byte slice.
	Sign(buf []byte) ([]byte, error)

	// Verify creates a signature for buf, comparing it against the URL-safe
	// base64 encoded sig. If the sig is invalid, then ErrInvalidSignature will
	// be returned.
	Verify(buf, sig []byte) ([]byte, error)

	// Encode encodes obj as a token.
	Encode(obj interface{}) ([]byte, error)

	// Decode decodes a serialized token, verifying the signature, storing the
	// decoded data from the token in obj.
	Decode(buf []byte, obj interface{}) error
}

// PEM is a wrapper that assists with loading PEM-encoded crypto primitives
// (ie, rsa.PrivateKey, ecdsa.PrivateKey, etc).
type PEM pemutil.PEM

const (
	// NONE provides a JWT signing method for NONE.
	//
	// NOTE: This is not implemented for security reasons.
	NONE Algorithm = iota

	// HS256 provides a JWT signing method for HMAC using SHA-256.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.2
	HS256

	// HS384 provides a JWT signing method for HMAC using SHA-384.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.2
	HS384

	// HS512 provides a JWT signing method for HMAC using SHA-512.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.2
	HS512

	// RS256 provides a JWT signing method for RSASSA-PKCS1-V1_5 using SHA-256.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.3
	RS256

	// RS384 provides a JWT signing method for RSASSA-PKCS1-V1_5 using SHA-384.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.3
	RS384

	// RS512 provides a JWT signing method for RSASSA-PKCS1-V1_5 using SHA-512.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.3
	RS512

	// ES256 provides a JWT signing method for ECDSA using P-256 and SHA-256.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.4
	ES256

	// ES384 provides a JWT signing method for ECDSA using P-384 and SHA-384.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.4
	ES384

	// ES512 provides a JWT signing method for ECDSA using P-521 and SHA-512.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.4
	ES512

	// PS256 provides a JWT signing method for RSASSA-PSS using SHA-256 and
	// MGF1 mask generation function with SHA-256.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.5
	PS256

	// PS384 provides a JWT signing method for RSASSA-PSS using SHA-384 hash
	// algorithm and MGF1 mask generation function with SHA-384.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.5
	PS384

	// PS512 provides a JWT signing method for RSASSA-PSS using SHA-512 hash
	// algorithm and MGF1 mask generation function with SHA-512.
	//
	// See http://tools.ietf.org/html/rfc7518#section-3.5
	PS512
)

// algMap is a map of algorithm implementations to its Algorithm.
var algMap = map[Algorithm]struct {
	NewFunc func(pemutil.Store, crypto.Hash) (Signer, error)
	Hash    crypto.Hash
}{
	// none
	NONE: {func(pemutil.Store, crypto.Hash) (Signer, error) {
		return nil, errors.New("jwt algorithm none is not implemented")
	}, crypto.SHA256},

	// HS256 is HMAC + SHA-256
	HS256: {NewHMACSigner(HS256), crypto.SHA256},

	// HS384 is HMAC + SHA-384
	HS384: {NewHMACSigner(HS384), crypto.SHA384},

	// HS512 is HMAC + SHA-512
	HS512: {NewHMACSigner(HS512), crypto.SHA512},

	// RS256 is RSASSA-PKCS1-V1_5 + SHA-256
	RS256: {NewRSASigner(RS256, PKCS1v15RSAMethod), crypto.SHA256},

	// RS384 is RSASSA-PKCS1-V1_5 + SHA-384
	RS384: {NewRSASigner(RS384, PKCS1v15RSAMethod), crypto.SHA384},

	// RS512 is RSASSA-PKCS1-V1_5 + SHA-512
	RS512: {NewRSASigner(RS512, PKCS1v15RSAMethod), crypto.SHA512},

	// ES256 is ECDSA P-256 + SHA-256
	ES256: {NewEllipticSigner(ES256, elliptic.P256()), crypto.SHA256},

	// ES384 is ECDSA P-384 + SHA-384
	ES384: {NewEllipticSigner(ES384, elliptic.P384()), crypto.SHA384},

	// ES512 is ECDSA P-521 + SHA-512
	ES512: {NewEllipticSigner(ES512, elliptic.P521()), crypto.SHA512},

	// PS256 is RSASSA-PSS + SHA-256
	PS256: {NewRSASigner(PS256, PSSRSAMethod), crypto.SHA256},

	// PS384 is RSASSA-PSS + SHA-384
	PS384: {NewRSASigner(PS384, PSSRSAMethod), crypto.SHA384},

	// PS512 is RSASSA-PSS + SHA-512
	PS512: {NewRSASigner(PS512, PSSRSAMethod), crypto.SHA512},
}

// New creates a Signer using the supplied keyset.
//
// The keyset can be of type PEM, pemutil.PEM, pemutil.Store,
// *rsa.{PrivateKey,PublicKey}, *ecdsa.{PrivateKey,PublicKey}, or []byte.
//
// PLEASE NOTE: if a calling package *DOES NOT* provide a private key, tokens
// cannot be Encode'd. Please note that pemutil will automatically generate the
// public key for a provided private key if none was provided in the keyset.
func (alg Algorithm) New(keyset interface{}) (Signer, error) {
	var err error

	a := algMap[alg]

	// check hash
	if !a.Hash.Available() {
		return nil, fmt.Errorf("%s.New: crypto hash unavailable", alg)
	}

	var store pemutil.Store

	// load the data
	switch p := keyset.(type) {
	case pemutil.Store:
		store = p

	// pem data
	case pemutil.PEM:
		store, err = loadKeysFromPEM(p)
		if err != nil {
			return nil, err
		}
	case PEM:
		store, err = loadKeysFromPEM(pemutil.PEM(p))
		if err != nil {
			return nil, err
		}

	// raw key
	case []byte:
		store = pemutil.Store{pemutil.PrivateKey: p}

	// rsa keys
	case *rsa.PrivateKey:
		store = pemutil.Store{pemutil.RSAPrivateKey: p}
	case *rsa.PublicKey:
		store = pemutil.Store{pemutil.PublicKey: p}

	// ecc keys
	case *ecdsa.PrivateKey:
		store = pemutil.Store{pemutil.ECPrivateKey: p}
	case *ecdsa.PublicKey:
		store = pemutil.Store{pemutil.PublicKey: p}

	default:
		return nil, fmt.Errorf("%s.New: unrecognized keyset type", alg)
	}

	// generate public keys
	err = pemutil.GeneratePublicKeys(store)
	if err != nil {
		return nil, err
	}

	return a.NewFunc(store, a.Hash)
}

// Header builds the JWT header for the algorithm.
func (alg Algorithm) Header() Header {
	return Header{
		Type:      "JWT",
		Algorithm: alg,
	}
}

// Encode encodes a JWT using the Algorithm and Signer.
func (alg Algorithm) Encode(signer Signer, obj interface{}) ([]byte, error) {
	return Encode(alg, signer, obj)
}

// Decode verifies the signature of a JWT against the Algorithm, decoding any
// data in buf to obj.
func (alg Algorithm) Decode(signer Signer, buf []byte, obj interface{}) error {
	return Decode(alg, signer, buf, obj)
}

// MarshalText marshals Algorithm into a standard string.
func (alg Algorithm) MarshalText() ([]byte, error) {
	return []byte(alg.String()), nil
}

// UnmarshalText unmarshals a string into the corresponding Algorithm.
func (alg *Algorithm) UnmarshalText(buf []byte) error {
	switch string(buf) {
	// hmac
	case "HS256":
		*alg = HS256
	case "HS384":
		*alg = HS384
	case "HS512":
		*alg = HS512

	// rsa-pkcs1v15
	case "RS256":
		*alg = RS256
	case "RS384":
		*alg = RS384
	case "RS512":
		*alg = RS512

	// ecc
	case "ES256":
		*alg = ES256
	case "ES384":
		*alg = ES384
	case "ES512":
		*alg = ES512

	// rsa-pss
	case "PS256":
		*alg = PS256
	case "PS384":
		*alg = PS384
	case "PS512":
		*alg = PS512

	// errors
	case "none":
		return errors.New("algorithm none not supported")
	default:
		return errors.New("algorithm not supported")
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/hex"
	"encoding/pem"
	"math/rand"
	"net/http"
	"time"

	"crypto/x509"
	"io/ioutil"

	"crypto/ecdsa"

	"github.com/underlx/disturbancesmlx/resource"
	"github.com/yarf-framework/yarf"
)

// Static defines a simple resource
type Static struct {
	yarf.Resource
	path  string // Directory to serve static files from.
	strip string // Part of path to strip from http file server handler
}

// WithPath associates the path and part of the path to strip with this resource
func (r *Static) WithPath(path string, strip string) *Static {
	r.path = path
	r.strip = strip
	return r
}

// Get implements the static files handler
func (r *Static) Get(c *yarf.Context) error {
	http.StripPrefix(r.strip, http.FileServer(http.Dir(r.path))).ServeHTTP(c.Response, c.Request)

	return nil
}

// APIserver sets up and starts the API server
func APIserver(trustedClientCertPath string) {
	y := yarf.New()

	v1 := yarf.RouteGroup("/v1")

	v1.Add("/meta", new(resource.Meta).WithNode(rootSqalxNode))

	v1.Add("/networks", new(resource.Network).WithNode(rootSqalxNode))
	v1.Add("/networks/:id", new(resource.Network).WithNode(rootSqalxNode))

	v1.Add("/lines", new(resource.Line).WithNode(rootSqalxNode))
	v1.Add("/lines/:id", new(resource.Line).WithNode(rootSqalxNode))

	v1.Add("/stations", new(resource.Station).WithNode(rootSqalxNode))
	v1.Add("/stations/:id", new(resource.Station).WithNode(rootSqalxNode))
	v1.Add("/stations/:sid/lobbies", new(resource.Lobby).WithNode(rootSqalxNode))

	v1.Add("/lobbies", new(resource.Lobby).WithNode(rootSqalxNode))
	v1.Add("/lobbies/:id", new(resource.Lobby).WithNode(rootSqalxNode))

	v1.Add("/pois", new(resource.POI).WithNode(rootSqalxNode))
	v1.Add("/pois/:id", new(resource.POI).WithNode(rootSqalxNode))

	v1.Add("/connections", new(resource.Connection).WithNode(rootSqalxNode))
	v1.Add("/connections/:from/:to", new(resource.Connection).WithNode(rootSqalxNode))

	v1.Add("/transfers", new(resource.Transfer).WithNode(rootSqalxNode))
	v1.Add("/transfers/:station/:from/:to", new(resource.Transfer).WithNode(rootSqalxNode))

	v1.Add("/disturbances", new(resource.Disturbance).WithNode(rootSqalxNode))
	v1.Add("/disturbances/:id", new(resource.Disturbance).WithNode(rootSqalxNode))

	v1.Add("/datasets", new(resource.Dataset).WithNode(rootSqalxNode).WithSquirrel(&sdb))
	v1.Add("/datasets/:id", new(resource.Dataset).WithNode(rootSqalxNode).WithSquirrel(&sdb))

	v1.Add("/stats", new(resource.Stats).WithNode(rootSqalxNode).WithStats(statsHandler))
	v1.Add("/stats/:id", new(resource.Stats).WithNode(rootSqalxNode).WithStats(statsHandler))

	v1.Add("/announcements", new(resource.Announcement).WithAnnouncementStore(&annStore))
	v1.Add("/announcements/:source", new(resource.Announcement).WithAnnouncementStore(&annStore))

	v1.Add("/stationkb/*", new(Static).WithPath("stationkb/", "/v1/stationkb/"))

	pubkey := getTrustedClientPublicKey(trustedClientCertPath)

	v1.Add("/pair", new(resource.Pair).
		WithNode(rootSqalxNode).
		WithPublicKey(pubkey).
		WithHashKey(getHashKey()))

	v1.Add("/authtest", new(resource.AuthTest).
		WithNode(rootSqalxNode).
		WithHashKey(getHashKey()))

	v1.Add("/trips", new(resource.Trip).WithNode(rootSqalxNode).WithHashKey(getHashKey()))
	v1.Add("/trips/:id", new(resource.Trip).WithNode(rootSqalxNode).WithHashKey(getHashKey()))

	v1.Add("/disturbances/reports", new(resource.DisturbanceReport).
		WithNode(rootSqalxNode).
		WithHashKey(getHashKey()).
		WithReportHandler(reportHandler))

	v1.Add("/rt", new(resource.Realtime).
		WithNode(rootSqalxNode).
		WithHashKey(getHashKey()).
		WithStatsHandler(statsHandler).
		WithVehicleHandler(vehicleHandler))

	v1.Add("/feedback", new(resource.Feedback).WithNode(rootSqalxNode).WithHashKey(getHashKey()))

	y.AddGroup(v1)
	if DEBUG {
		y.Insert(new(DelayMiddleware))
	}

	y.Insert(new(TelemetryMiddleware))

	y.Logger = webLog
	y.Start(":12000")
}

// DelayMiddleware injects a semi-random delay before each request is processed, for debugging
type DelayMiddleware struct {
	yarf.Middleware
}

// PreDispatch runs before the request is dispatched
func (m *DelayMiddleware) PreDispatch(c *yarf.Context) error {
	time.Sleep(500*time.Millisecond + time.Duration(rand.Intn(500))*time.Millisecond)
	return nil
}

// TelemetryMiddleware collects statistics about API usage
type TelemetryMiddleware struct {
	yarf.Middleware
}

// PostDispatch runs after the request is dispatched
func (m *TelemetryMiddleware) PostDispatch(c *yarf.Context) error {
	// non-blocking send
	select {
	case APIrequestTelemetry <- true:
	default:
	}

	return nil
}

func getTrustedClientPublicKey(trustedClientCertPath string) *ecdsa.PublicKey {
	certBytes, err := ioutil.ReadFile(trustedClientCertPath)
	if err != nil {
		panic("Error reading trusted client certificate")
	}
	block, _ := pem.Decode([]byte(certBytes))
	cert, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		panic("Error parsing client certificate: " + err.Error())
	}

	return cert.PublicKey.(*ecdsa.PublicKey)
}

func getHashKey() []byte {
	hexkey, present := secrets.Get("secretHMACkey")
	if !present {
		mainLog.Fatal("API secret HMAC key not present in keybox")
	}
	key, err := hex.DecodeString(hexkey)
	if err != nil {
		mainLog.Fatal("Invalid API secret HMAC key specified")
	}
	return key
}
<commit_msg>Add yarf router resources in the correct order<commit_after>package main

import (
	"encoding/hex"
	"encoding/pem"
	"math/rand"
	"net/http"
	"time"

	"crypto/x509"
	"io/ioutil"

	"crypto/ecdsa"

	"github.com/underlx/disturbancesmlx/resource"
	"github.com/yarf-framework/yarf"
)

// Static defines a simple resource
type Static struct {
	yarf.Resource
	path  string // Directory to serve static files from.
	strip string // Part of path to strip from http file server handler
}

// WithPath associates the path and part of the path to strip with this resource
func (r *Static) WithPath(path string, strip string) *Static {
	r.path = path
	r.strip = strip
	return r
}

// Get implements the static files handler
func (r *Static) Get(c *yarf.Context) error {
	http.StripPrefix(r.strip, http.FileServer(http.Dir(r.path))).ServeHTTP(c.Response, c.Request)

	return nil
}

// APIserver sets up and starts the API server
func APIserver(trustedClientCertPath string) {
	y := yarf.New()

	v1 := yarf.RouteGroup("/v1")

	v1.Add("/meta", new(resource.Meta).WithNode(rootSqalxNode))

	v1.Add("/networks", new(resource.Network).WithNode(rootSqalxNode))
	v1.Add("/networks/:id", new(resource.Network).WithNode(rootSqalxNode))

	v1.Add("/lines", new(resource.Line).WithNode(rootSqalxNode))
	v1.Add("/lines/:id", new(resource.Line).WithNode(rootSqalxNode))

	v1.Add("/stations", new(resource.Station).WithNode(rootSqalxNode))
	v1.Add("/stations/:id", new(resource.Station).WithNode(rootSqalxNode))
	v1.Add("/stations/:sid/lobbies", new(resource.Lobby).WithNode(rootSqalxNode))

	v1.Add("/lobbies", new(resource.Lobby).WithNode(rootSqalxNode))
	v1.Add("/lobbies/:id", new(resource.Lobby).WithNode(rootSqalxNode))

	v1.Add("/pois", new(resource.POI).WithNode(rootSqalxNode))
	v1.Add("/pois/:id", new(resource.POI).WithNode(rootSqalxNode))

	v1.Add("/connections", new(resource.Connection).WithNode(rootSqalxNode))
	v1.Add("/connections/:from/:to", new(resource.Connection).WithNode(rootSqalxNode))

	v1.Add("/transfers", new(resource.Transfer).WithNode(rootSqalxNode))
	v1.Add("/transfers/:station/:from/:to", new(resource.Transfer).WithNode(rootSqalxNode))

	v1.Add("/disturbances", new(resource.Disturbance).WithNode(rootSqalxNode))

	v1.Add("/disturbances/reports", new(resource.DisturbanceReport).
		WithNode(rootSqalxNode).
		WithHashKey(getHashKey()).
		WithReportHandler(reportHandler))

	v1.Add("/disturbances/:id", new(resource.Disturbance).WithNode(rootSqalxNode))

	v1.Add("/datasets", new(resource.Dataset).WithNode(rootSqalxNode).WithSquirrel(&sdb))
	v1.Add("/datasets/:id", new(resource.Dataset).WithNode(rootSqalxNode).WithSquirrel(&sdb))

	v1.Add("/stats", new(resource.Stats).WithNode(rootSqalxNode).WithStats(statsHandler))
	v1.Add("/stats/:id", new(resource.Stats).WithNode(rootSqalxNode).WithStats(statsHandler))

	v1.Add("/announcements", new(resource.Announcement).WithAnnouncementStore(&annStore))
	v1.Add("/announcements/:source", new(resource.Announcement).WithAnnouncementStore(&annStore))

	v1.Add("/stationkb/*", new(Static).WithPath("stationkb/", "/v1/stationkb/"))

	pubkey := getTrustedClientPublicKey(trustedClientCertPath)

	v1.Add("/pair", new(resource.Pair).
		WithNode(rootSqalxNode).
		WithPublicKey(pubkey).
		WithHashKey(getHashKey()))

	v1.Add("/authtest", new(resource.AuthTest).
		WithNode(rootSqalxNode).
		WithHashKey(getHashKey()))

	v1.Add("/trips", new(resource.Trip).WithNode(rootSqalxNode).WithHashKey(getHashKey()))
	v1.Add("/trips/:id", new(resource.Trip).WithNode(rootSqalxNode).WithHashKey(getHashKey()))

	v1.Add("/rt", new(resource.Realtime).
		WithNode(rootSqalxNode).
		WithHashKey(getHashKey()).
		WithStatsHandler(statsHandler).
		WithVehicleHandler(vehicleHandler))

	v1.Add("/feedback", new(resource.Feedback).WithNode(rootSqalxNode).WithHashKey(getHashKey()))

	y.AddGroup(v1)
	if DEBUG {
		y.Insert(new(DelayMiddleware))
	}

	y.Insert(new(TelemetryMiddleware))

	y.Logger = webLog
	y.Start(":12000")
}

// DelayMiddleware injects a semi-random delay before each request is processed, for debugging
type DelayMiddleware struct {
	yarf.Middleware
}

// PreDispatch runs before the request is dispatched
func (m *DelayMiddleware) PreDispatch(c *yarf.Context) error {
	time.Sleep(500*time.Millisecond + time.Duration(rand.Intn(500))*time.Millisecond)
	return nil
}

// TelemetryMiddleware collects statistics about API usage
type TelemetryMiddleware struct {
	yarf.Middleware
}

// PostDispatch runs after the request is dispatched
func (m *TelemetryMiddleware) PostDispatch(c *yarf.Context) error {
	// non-blocking send
	select {
	case APIrequestTelemetry <- true:
	default:
	}

	return nil
}

func getTrustedClientPublicKey(trustedClientCertPath string) *ecdsa.PublicKey {
	certBytes, err := ioutil.ReadFile(trustedClientCertPath)
	if err != nil {
		panic("Error reading trusted client certificate")
	}
	block, _ := pem.Decode([]byte(certBytes))
	cert, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		panic("Error parsing client certificate: " + err.Error())
	}

	return cert.PublicKey.(*ecdsa.PublicKey)
}

func getHashKey() []byte {
	hexkey, present := secrets.Get("secretHMACkey")
	if !present {
		mainLog.Fatal("API secret HMAC key not present in keybox")
	}
	key, err := hex.DecodeString(hexkey)
	if err != nil {
		mainLog.Fatal("Invalid API secret HMAC key specified")
	}
	return key
}
<|endoftext|>


<commit_before>package telebot

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
)

func sendCommand(method string, token string, params url.Values) ([]byte, error) {
	url := fmt.Sprintf("https://api.telegram.org/bot%s/%s?%s",
		token, method, params.Encode())

	resp, err := http.Get(url)
	if err != nil {
		return []byte{}, err
	}

	defer resp.Body.Close()
	json, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return []byte{}, err
	}

	return json, nil
}

func sendFile(method, token, name, path string, params url.Values) ([]byte, error) {
	file, err := os.Open(path)
	if err != nil {
		return []byte{}, err
	}
	defer file.Close()

	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)
	part, err := writer.CreateFormFile(name, filepath.Base(path))
	if err != nil {
		return []byte{}, err
	}

	if _, err = io.Copy(part, file); err != nil {
		return []byte{}, err
	}

	for field, values := range params {
		if len(values) > 0 {
			writer.WriteField(field, values[0])
		}
	}

	if err = writer.Close(); err != nil {
		return []byte{}, err
	}

	url := fmt.Sprintf("https://api.telegram.org/bot%s/%s", token, method)
	req, err := http.NewRequest("POST", url, body)
	if err != nil {
		return []byte{}, err
	}

	req.Header.Add("Content-Type", writer.FormDataContentType())

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return []byte{}, err
	}

	json, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return []byte{}, err
	}

	return json, nil
}

func embedSendOptions(params *url.Values, options *SendOptions) {
	if params == nil || options == nil {
		return
	}

	if options.ReplyTo.ID != 0 {
		params.Set("reply_to_message_id", strconv.Itoa(options.ReplyTo.ID))
	}

	if options.DisableWebPagePreview {
		params.Set("disable_web_page_preview", "true")
	}

	// process reply_markup
	if options.ReplyMarkup.ForceReply || options.ReplyMarkup.CustomKeyboard != nil || options.ReplyMarkup.HideCustomKeyboard {
		replyMarkup, _ := json.Marshal(options.ReplyMarkup)
		params.Set("reply_markup", string(replyMarkup))
	}
}

func getMe(token string) (User, error) {
	meJSON, err := sendCommand("getMe", token, url.Values{})
	if err != nil {
		return User{}, err
	}

	var botInfo struct {
		Ok          bool
		Result      User
		Description string
	}

	err = json.Unmarshal(meJSON, &botInfo)
	if err != nil {
		return User{}, AuthError{"invalid token"}
	}

	if botInfo.Ok {
		return botInfo.Result, nil
	}

	return User{}, AuthError{botInfo.Description}
}

func getUpdates(token string, offset int, timeout int) (updates []Update, err error) {
	params := url.Values{}
	params.Set("offset", strconv.Itoa(offset))
	params.Set("timeout", strconv.Itoa(timeout))
	updatesJSON, err := sendCommand("getUpdates", token, params)
	if err != nil {
		return
	}

	var updatesRecieved struct {
		Ok          bool
		Result      []Update
		Description string
	}

	err = json.Unmarshal(updatesJSON, &updatesRecieved)
	if err != nil {
		return
	}

	if !updatesRecieved.Ok {
		err = FetchError{updatesRecieved.Description}
		return
	}

	updates = updatesRecieved.Result
	return
}
<commit_msg>Change API to return error on 500 http status.<commit_after>package telebot

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
)

func sendCommand(method string, token string, params url.Values) ([]byte, error) {
	url := fmt.Sprintf("https://api.telegram.org/bot%s/%s?%s",
		token, method, params.Encode())

	resp, err := http.Get(url)
	if err != nil {
		return []byte{}, err
	}

	defer resp.Body.Close()
	json, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return []byte{}, err
	}

	return json, nil
}

func sendFile(method, token, name, path string, params url.Values) ([]byte, error) {
	file, err := os.Open(path)
	if err != nil {
		return []byte{}, err
	}
	defer file.Close()

	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)
	part, err := writer.CreateFormFile(name, filepath.Base(path))
	if err != nil {
		return []byte{}, err
	}

	if _, err = io.Copy(part, file); err != nil {
		return []byte{}, err
	}

	for field, values := range params {
		if len(values) > 0 {
			writer.WriteField(field, values[0])
		}
	}

	if err = writer.Close(); err != nil {
		return []byte{}, err
	}

	url := fmt.Sprintf("https://api.telegram.org/bot%s/%s", token, method)
	req, err := http.NewRequest("POST", url, body)
	if err != nil {
		return []byte{}, err
	}

	req.Header.Add("Content-Type", writer.FormDataContentType())

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return []byte{}, err
	}

	if resp.StatusCode == http.StatusInternalServerError {
		return []byte{}, fmt.Errorf("Telegram API returned 500 internal server error")
	}

	json, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return []byte{}, err
	}

	return json, nil
}

func embedSendOptions(params *url.Values, options *SendOptions) {
	if params == nil || options == nil {
		return
	}

	if options.ReplyTo.ID != 0 {
		params.Set("reply_to_message_id", strconv.Itoa(options.ReplyTo.ID))
	}

	if options.DisableWebPagePreview {
		params.Set("disable_web_page_preview", "true")
	}

	// process reply_markup
	if options.ReplyMarkup.ForceReply || options.ReplyMarkup.CustomKeyboard != nil || options.ReplyMarkup.HideCustomKeyboard {
		replyMarkup, _ := json.Marshal(options.ReplyMarkup)
		params.Set("reply_markup", string(replyMarkup))
	}
}

func getMe(token string) (User, error) {
	meJSON, err := sendCommand("getMe", token, url.Values{})
	if err != nil {
		return User{}, err
	}

	var botInfo struct {
		Ok          bool
		Result      User
		Description string
	}

	err = json.Unmarshal(meJSON, &botInfo)
	if err != nil {
		return User{}, AuthError{"invalid token"}
	}

	if botInfo.Ok {
		return botInfo.Result, nil
	}

	return User{}, AuthError{botInfo.Description}
}

func getUpdates(token string, offset int, timeout int) (updates []Update, err error) {
	params := url.Values{}
	params.Set("offset", strconv.Itoa(offset))
	params.Set("timeout", strconv.Itoa(timeout))
	updatesJSON, err := sendCommand("getUpdates", token, params)
	if err != nil {
		return
	}

	var updatesRecieved struct {
		Ok          bool
		Result      []Update
		Description string
	}

	err = json.Unmarshal(updatesJSON, &updatesRecieved)
	if err != nil {
		return
	}

	if !updatesRecieved.Ok {
		err = FetchError{updatesRecieved.Description}
		return
	}

	updates = updatesRecieved.Result
	return
}
<|endoftext|>


<commit_before>package blankhost

import (
	"context"
	"io"

	logging "github.com/ipfs/go-log"
	host "github.com/libp2p/go-libp2p-host"
	inet "github.com/libp2p/go-libp2p-net"
	peer "github.com/libp2p/go-libp2p-peer"
	pstore "github.com/libp2p/go-libp2p-peerstore"
	protocol "github.com/libp2p/go-libp2p-protocol"
	ma "github.com/multiformats/go-multiaddr"
	mstream "github.com/multiformats/go-multistream"
)

var log = logging.Logger("blankhost")

// BlankHost is the thinnest implementation of the host.Host interface
type BlankHost struct {
	n   inet.Network
	mux *mstream.MultistreamMuxer
}

func NewBlankHost(n inet.Network) *BlankHost {
	bh := &BlankHost{
		n:   n,
		mux: mstream.NewMultistreamMuxer(),
	}

	n.SetStreamHandler(bh.newStreamHandler)
	return bh
}

var _ host.Host = (*BlankHost)(nil)

func (bh *BlankHost) Addrs() []ma.Multiaddr {
	addrs, err := bh.n.InterfaceListenAddresses()
	if err != nil {
		log.Debug("error retrieving network interface addrs: ", err)
		return nil
	}

	return addrs
}

func (bh *BlankHost) Close() error {
	return bh.n.Close()
}

func (bh *BlankHost) Connect(ctx context.Context, pi pstore.PeerInfo) error {
	// absorb addresses into peerstore
	bh.Peerstore().AddAddrs(pi.ID, pi.Addrs, pstore.TempAddrTTL)

	cs := bh.n.ConnsToPeer(pi.ID)
	if len(cs) > 0 {
		return nil
	}

	_, err := bh.Network().DialPeer(ctx, pi.ID)
	return err
}

func (bh *BlankHost) Peerstore() pstore.Peerstore {
	return bh.n.Peerstore()
}

func (bh *BlankHost) ID() peer.ID {
	return bh.n.LocalPeer()
}

func (bh *BlankHost) NewStream(ctx context.Context, p peer.ID, protos ...protocol.ID) (inet.Stream, error) {
	s, err := bh.n.NewStream(ctx, p)
	if err != nil {
		return nil, err
	}

	var protoStrs []string
	for _, pid := range protos {
		protoStrs = append(protoStrs, string(pid))
	}

	log.Error("select: ", protoStrs)
	selected, err := mstream.SelectOneOf(protoStrs, s)
	if err != nil {
		s.Close()
		return nil, err
	}

	selpid := protocol.ID(selected)
	s.SetProtocol(selpid)
	bh.Peerstore().AddProtocols(p, selected)

	return s, nil
}

func (bh *BlankHost) RemoveStreamHandler(p protocol.ID) {
	bh.Mux().RemoveHandler(string(p))
}

func (bh *BlankHost) SetStreamHandler(pid protocol.ID, handler inet.StreamHandler) {
	bh.Mux().AddHandler(string(pid), func(p string, rwc io.ReadWriteCloser) error {
		is := rwc.(inet.Stream)
		is.SetProtocol(protocol.ID(p))
		handler(is)
		return nil
	})
}

func (bh *BlankHost) SetStreamHandlerMatch(pid protocol.ID, m func(string) bool, handler inet.StreamHandler) {
	bh.Mux().AddHandlerWithFunc(string(pid), m, func(p string, rwc io.ReadWriteCloser) error {
		is := rwc.(inet.Stream)
		is.SetProtocol(protocol.ID(p))
		handler(is)
		return nil
	})
}

// newStreamHandler is the remote-opened stream handler for inet.Network
func (h *BlankHost) newStreamHandler(s inet.Stream) {

	protoID, handle, err := h.Mux().Negotiate(s)
	if err != nil {
		log.Warning("protocol mux failed: %s", err)
		s.Close()
		return
	}

	s.SetProtocol(protocol.ID(protoID))

	go handle(protoID, s)
}

// TODO: i'm not sure this really needs to be here
func (bh *BlankHost) Mux() *mstream.MultistreamMuxer {
	return bh.mux
}

// TODO: also not sure this fits... Might be better ways around this (leaky abstractions)
func (bh *BlankHost) Network() inet.Network {
	return bh.n
}
<commit_msg>gx publish 0.1.6<commit_after>package blankhost

import (
	"context"
	"io"

	logging "github.com/ipfs/go-log"
	host "github.com/libp2p/go-libp2p-host"
	inet "github.com/libp2p/go-libp2p-net"
	peer "github.com/libp2p/go-libp2p-peer"
	pstore "github.com/libp2p/go-libp2p-peerstore"
	protocol "github.com/libp2p/go-libp2p-protocol"
	ma "github.com/multiformats/go-multiaddr"
	mstream "github.com/multiformats/go-multistream"
)

var log = logging.Logger("blankhost")

// BlankHost is the thinnest implementation of the host.Host interface
type BlankHost struct {
	n   inet.Network
	mux *mstream.MultistreamMuxer
}

func NewBlankHost(n inet.Network) *BlankHost {
	bh := &BlankHost{
		n:   n,
		mux: mstream.NewMultistreamMuxer(),
	}

	n.SetStreamHandler(bh.newStreamHandler)
	return bh
}

var _ host.Host = (*BlankHost)(nil)

func (bh *BlankHost) Addrs() []ma.Multiaddr {
	addrs, err := bh.n.InterfaceListenAddresses()
	if err != nil {
		log.Debug("error retrieving network interface addrs: ", err)
		return nil
	}

	return addrs
}

func (bh *BlankHost) Close() error {
	return bh.n.Close()
}

func (bh *BlankHost) Connect(ctx context.Context, pi pstore.PeerInfo) error {
	// absorb addresses into peerstore
	bh.Peerstore().AddAddrs(pi.ID, pi.Addrs, pstore.TempAddrTTL)

	cs := bh.n.ConnsToPeer(pi.ID)
	if len(cs) > 0 {
		return nil
	}

	_, err := bh.Network().DialPeer(ctx, pi.ID)
	return err
}

func (bh *BlankHost) Peerstore() pstore.Peerstore {
	return bh.n.Peerstore()
}

func (bh *BlankHost) ID() peer.ID {
	return bh.n.LocalPeer()
}

func (bh *BlankHost) NewStream(ctx context.Context, p peer.ID, protos ...protocol.ID) (inet.Stream, error) {
	s, err := bh.n.NewStream(ctx, p)
	if err != nil {
		return nil, err
	}

	var protoStrs []string
	for _, pid := range protos {
		protoStrs = append(protoStrs, string(pid))
	}

	selected, err := mstream.SelectOneOf(protoStrs, s)
	if err != nil {
		s.Close()
		return nil, err
	}

	selpid := protocol.ID(selected)
	s.SetProtocol(selpid)
	bh.Peerstore().AddProtocols(p, selected)

	return s, nil
}

func (bh *BlankHost) RemoveStreamHandler(p protocol.ID) {
	bh.Mux().RemoveHandler(string(p))
}

func (bh *BlankHost) SetStreamHandler(pid protocol.ID, handler inet.StreamHandler) {
	bh.Mux().AddHandler(string(pid), func(p string, rwc io.ReadWriteCloser) error {
		is := rwc.(inet.Stream)
		is.SetProtocol(protocol.ID(p))
		handler(is)
		return nil
	})
}

func (bh *BlankHost) SetStreamHandlerMatch(pid protocol.ID, m func(string) bool, handler inet.StreamHandler) {
	bh.Mux().AddHandlerWithFunc(string(pid), m, func(p string, rwc io.ReadWriteCloser) error {
		is := rwc.(inet.Stream)
		is.SetProtocol(protocol.ID(p))
		handler(is)
		return nil
	})
}

// newStreamHandler is the remote-opened stream handler for inet.Network
func (h *BlankHost) newStreamHandler(s inet.Stream) {

	protoID, handle, err := h.Mux().Negotiate(s)
	if err != nil {
		log.Warning("protocol mux failed: %s", err)
		s.Close()
		return
	}

	s.SetProtocol(protocol.ID(protoID))

	go handle(protoID, s)
}

// TODO: i'm not sure this really needs to be here
func (bh *BlankHost) Mux() *mstream.MultistreamMuxer {
	return bh.mux
}

// TODO: also not sure this fits... Might be better ways around this (leaky abstractions)
func (bh *BlankHost) Network() inet.Network {
	return bh.n
}
<|endoftext|>


<commit_before>// Package chronos is a scheduling tool for Go based on:
//  https://github.com/carlescere/scheduler

package chronos

import (
	"time"
	//"sync"
)

type Job struct {
	task   func() // Task to be scheduled
	times, // Times that it can be executed, -1 means no limit
	n int // Times that it has been executed
	aux      auxiliar   // Holds the values for following API calls
	schedule *scheduler // Scheduler to determine when to run the job
	quit,    // Channel for quitting the scheduled job
	skip chan struct{} // Channel for executing the task inmediately
	// TODO: add a lock
}

// Job construction with task assignment
func Schedule(f func()) *Job {
	return &Job{task: f, times: -1, quit: make(chan struct{}, 1),
		skip: make(chan struct{}, 1)}
}

// Defining the number of times
func (j *Job) NTimes(n int) *Job {
	j.times = n
	return j
}

func (j *Job) Once() *Job {
	return j.NTimes(1)
}

func (j *Job) Twice() *Job {
	return j.NTimes(2)
}

// Defining the period size in units
func (j *Job) Every(times ...int) *Job {
	switch len(times) {
	case 0:
		j.aux.ammount = 1
	case 1:
		j.aux.ammount = times[0]
	default:
		panic("Too many arguments in Job.Every()")
	}
	return j
}

// Defining the period's unit duration
func (j *Job) duration(d time.Duration) *Job {
	j.aux.kind = periodicKind
	j.aux.unit = d
	return j
}

func (j *Job) Nanosecond() *Job {
	return j.duration(time.Nanosecond)
}

func (j *Job) Nanoseconds() *Job {
	return j.Nanosecond()
}

func (j *Job) Microsecond() *Job {
	return j.duration(time.Microsecond)
}

func (j *Job) Microseconds() *Job {
	return j.Microsecond()
}

func (j *Job) Millisecond() *Job {
	return j.duration(time.Millisecond)
}

func (j *Job) Milliseconds() *Job {
	return j.Millisecond()
}

func (j *Job) Second() *Job {
	return j.duration(time.Second)
}

func (j *Job) Seconds() *Job {
	return j.Second()
}

func (j *Job) Minute() *Job {
	return j.duration(time.Minute)
}

func (j *Job) Minutes() *Job {
	return j.Minute()
}

func (j *Job) Hour() *Job {
	return j.duration(time.Hour)
}

func (j *Job) Hours() *Job {
	return j.Hour()
}

func (j *Job) Day() *Job {
	return j.duration(Day)
}

func (j *Job) Days() *Job {
	return j.Day()
}

func (j *Job) Week() *Job {
	return j.duration(Week)
}

func (j *Job) Weeks() *Job {
	return j.Week()
}

func (j *Job) Month() *Job {
	j.aux.kind = monthlyKind
	return j
}

func (j *Job) Months() *Job {
	return j.Month()
}

func (j *Job) Year() *Job {
	j.aux.kind = yearlyKind
	return j
}

func (j *Job) Years() *Job {
	return j.Year()
}

// Defining if it should run at the start of the cycle
func (j *Job) NotInmediately() *Job {
	j.aux.notInmediately = true
	return j
}

// Defining the starting and ending times
func (j *Job) At(t time.Time) *Job {
	j.aux.start = t
	return j
}

func (j *Job) In(d time.Duration) *Job {
	return j.At(time.Now().Add(d))
}

func (j *Job) Until(t time.Time) *Job {
	j.aux.end = t
	return j
}

// Scheduling the task
func (j *Job) Done() (error, chan struct{}, chan struct{}) {
	var err error
	switch j.aux.kind {
	case periodicKind:
		j.schedule, err = newPeriodic(j.aux.start, j.aux.end, j.aux.ammount,
			j.aux.unit, j.aux.notInmediately)
	case monthlyKind:
		j.schedule, err = newMonthly(j.aux.start, j.aux.end, j.aux.ammount,
			j.aux.notInmediately)
	case yearlyKind:
		j.schedule, err = newYearly(j.aux.start, j.aux.end, j.aux.ammount,
			j.aux.notInmediately)
	}

	if err == nil {
		go func(j *Job) {
			select {
			case <-j.quit:
				return
			case <-j.skip:
				go j.run()
			case <-timer.C:
				go j.run()
			}
		}(j)
	}

	return err, j.skip, j.quit
}

func (j *Job) run() {
	// Lock
	if j.times == -1 || j.n < j.times {
		j.n++
		j.task()
	}
	// Unlock
}
<commit_msg>Godoc adjustments<commit_after>// Package chronos is a scheduling tool for Go based on:
//  https://github.com/carlescere/scheduler

package chronos

import (
	"time"
	//"sync"
)

type Job struct {
	task   func() // Task to be scheduled
	times, // Times that it can be executed, -1 means no limit
	n int // Times that it has been executed
	aux      auxiliar   // Holds the values for following API calls
	schedule *scheduler // Scheduler to determine when to run the job
	quit,    // Channel for quitting the scheduled job
	skip chan struct{} // Channel for executing the task inmediately
	// TODO: add a lock
}

// Job construction with task assignment

func Schedule(f func()) *Job {
	return &Job{task: f, times: -1, quit: make(chan struct{}, 1),
		skip: make(chan struct{}, 1)}
}

// Defining the number of times

func (j *Job) NTimes(n int) *Job {
	j.times = n
	return j
}

func (j *Job) Once() *Job {
	return j.NTimes(1)
}

func (j *Job) Twice() *Job {
	return j.NTimes(2)
}

// Defining the period size in units

func (j *Job) Every(times ...int) *Job {
	switch len(times) {
	case 0:
		j.aux.ammount = 1
	case 1:
		j.aux.ammount = times[0]
	default:
		panic("Too many arguments in Job.Every()")
	}
	return j
}

// Defining the period's unit duration

func (j *Job) duration(d time.Duration) *Job {
	j.aux.kind = periodicKind
	j.aux.unit = d
	return j
}

func (j *Job) Nanosecond() *Job {
	return j.duration(time.Nanosecond)
}

func (j *Job) Nanoseconds() *Job {
	return j.Nanosecond()
}

func (j *Job) Microsecond() *Job {
	return j.duration(time.Microsecond)
}

func (j *Job) Microseconds() *Job {
	return j.Microsecond()
}

func (j *Job) Millisecond() *Job {
	return j.duration(time.Millisecond)
}

func (j *Job) Milliseconds() *Job {
	return j.Millisecond()
}

func (j *Job) Second() *Job {
	return j.duration(time.Second)
}

func (j *Job) Seconds() *Job {
	return j.Second()
}

func (j *Job) Minute() *Job {
	return j.duration(time.Minute)
}

func (j *Job) Minutes() *Job {
	return j.Minute()
}

func (j *Job) Hour() *Job {
	return j.duration(time.Hour)
}

func (j *Job) Hours() *Job {
	return j.Hour()
}

func (j *Job) Day() *Job {
	return j.duration(Day)
}

func (j *Job) Days() *Job {
	return j.Day()
}

func (j *Job) Week() *Job {
	return j.duration(Week)
}

func (j *Job) Weeks() *Job {
	return j.Week()
}

func (j *Job) Month() *Job {
	j.aux.kind = monthlyKind
	return j
}

func (j *Job) Months() *Job {
	return j.Month()
}

func (j *Job) Year() *Job {
	j.aux.kind = yearlyKind
	return j
}

func (j *Job) Years() *Job {
	return j.Year()
}

// Defining if it should run at the start of the cycle

func (j *Job) NotInmediately() *Job {
	j.aux.notInmediately = true
	return j
}

// Defining the starting and ending times

func (j *Job) At(t time.Time) *Job {
	j.aux.start = t
	return j
}

func (j *Job) In(d time.Duration) *Job {
	return j.At(time.Now().Add(d))
}

func (j *Job) Until(t time.Time) *Job {
	j.aux.end = t
	return j
}

// Scheduling the task

func (j *Job) Done() (error, chan struct{}, chan struct{}) {
	var err error
	switch j.aux.kind {
	case periodicKind:
		j.schedule, err = newPeriodic(j.aux.start, j.aux.end, j.aux.ammount,
			j.aux.unit, j.aux.notInmediately)
	case monthlyKind:
		j.schedule, err = newMonthly(j.aux.start, j.aux.end, j.aux.ammount,
			j.aux.notInmediately)
	case yearlyKind:
		j.schedule, err = newYearly(j.aux.start, j.aux.end, j.aux.ammount,
			j.aux.notInmediately)
	}

	if err == nil {
		go func(j *Job) {
			select {
			case <-j.quit:
				return
			case <-j.skip:
				go j.run()
			case <-timer.C:
				go j.run()
			}
		}(j)
	}

	return err, j.skip, j.quit
}

func (j *Job) run() {
	// Lock
	if j.times == -1 || j.n < j.times {
		j.n++
		j.task()
	}
	// Unlock
}
<|endoftext|>


<commit_before>package blankhost

import (
	"context"
	"errors"
	"fmt"
	"io"

	"github.com/libp2p/go-libp2p-core/connmgr"
	"github.com/libp2p/go-libp2p-core/event"
	"github.com/libp2p/go-libp2p-core/host"
	"github.com/libp2p/go-libp2p-core/network"
	"github.com/libp2p/go-libp2p-core/peer"
	"github.com/libp2p/go-libp2p-core/peerstore"
	"github.com/libp2p/go-libp2p-core/protocol"
	"github.com/libp2p/go-libp2p-core/record"

	"github.com/libp2p/go-eventbus"

	logging "github.com/ipfs/go-log/v2"

	ma "github.com/multiformats/go-multiaddr"
	mstream "github.com/multiformats/go-multistream"
)

var log = logging.Logger("blankhost")

// BlankHost is the thinnest implementation of the host.Host interface
type BlankHost struct {
	n        network.Network
	mux      *mstream.MultistreamMuxer
	cmgr     connmgr.ConnManager
	eventbus event.Bus
	emitters struct {
		evtLocalProtocolsUpdated event.Emitter
	}
}

type config struct {
	cmgr connmgr.ConnManager
}

type Option = func(cfg *config)

func WithConnectionManager(cmgr connmgr.ConnManager) Option {
	return func(cfg *config) {
		cfg.cmgr = cmgr
	}
}

func NewBlankHost(n network.Network, options ...Option) *BlankHost {
	cfg := config{
		cmgr: &connmgr.NullConnMgr{},
	}
	for _, opt := range options {
		opt(&cfg)
	}

	bh := &BlankHost{
		n:        n,
		cmgr:     cfg.cmgr,
		mux:      mstream.NewMultistreamMuxer(),
		eventbus: eventbus.NewBus(),
	}

	// subscribe the connection manager to network notifications (has no effect with NullConnMgr)
	n.Notify(bh.cmgr.Notifee())

	var err error
	if bh.emitters.evtLocalProtocolsUpdated, err = bh.eventbus.Emitter(&event.EvtLocalProtocolsUpdated{}); err != nil {
		return nil
	}
	evtPeerConnectednessChanged, err := bh.eventbus.Emitter(&event.EvtPeerConnectednessChanged{})
	if err != nil {
		return nil
	}
	n.Notify(newPeerConnectWatcher(evtPeerConnectednessChanged))

	n.SetStreamHandler(bh.newStreamHandler)

	// persist a signed peer record for self to the peerstore.
	if err := bh.initSignedRecord(); err != nil {
		log.Errorf("error creating blank host, err=%s", err)
		return nil
	}

	return bh
}

func (bh *BlankHost) initSignedRecord() error {
	cab, ok := peerstore.GetCertifiedAddrBook(bh.n.Peerstore())
	if !ok {
		log.Error("peerstore does not support signed records")
		return errors.New("peerstore does not support signed records")
	}
	rec := peer.PeerRecordFromAddrInfo(peer.AddrInfo{ID: bh.ID(), Addrs: bh.Addrs()})
	ev, err := record.Seal(rec, bh.Peerstore().PrivKey(bh.ID()))
	if err != nil {
		log.Errorf("failed to create signed record for self, err=%s", err)
		return fmt.Errorf("failed to create signed record for self, err=%s", err)
	}
	_, err = cab.ConsumePeerRecord(ev, peerstore.PermanentAddrTTL)
	if err != nil {
		log.Errorf("failed to persist signed record to peerstore,err=%s", err)
		return fmt.Errorf("failed to persist signed record for self, err=%s", err)
	}
	return err
}

var _ host.Host = (*BlankHost)(nil)

func (bh *BlankHost) Addrs() []ma.Multiaddr {
	addrs, err := bh.n.InterfaceListenAddresses()
	if err != nil {
		log.Debug("error retrieving network interface addrs: ", err)
		return nil
	}

	return addrs
}

func (bh *BlankHost) Close() error {
	return bh.n.Close()
}

func (bh *BlankHost) Connect(ctx context.Context, ai peer.AddrInfo) error {
	// absorb addresses into peerstore
	bh.Peerstore().AddAddrs(ai.ID, ai.Addrs, peerstore.TempAddrTTL)

	cs := bh.n.ConnsToPeer(ai.ID)
	if len(cs) > 0 {
		return nil
	}

	_, err := bh.Network().DialPeer(ctx, ai.ID)
	return err
}

func (bh *BlankHost) Peerstore() peerstore.Peerstore {
	return bh.n.Peerstore()
}

func (bh *BlankHost) ID() peer.ID {
	return bh.n.LocalPeer()
}

func (bh *BlankHost) NewStream(ctx context.Context, p peer.ID, protos ...protocol.ID) (network.Stream, error) {
	s, err := bh.n.NewStream(ctx, p)
	if err != nil {
		return nil, err
	}

	var protoStrs []string
	for _, pid := range protos {
		protoStrs = append(protoStrs, string(pid))
	}

	selected, err := mstream.SelectOneOf(protoStrs, s)
	if err != nil {
		s.Reset()
		return nil, err
	}

	selpid := protocol.ID(selected)
	s.SetProtocol(selpid)
	bh.Peerstore().AddProtocols(p, selected)

	return s, nil
}

func (bh *BlankHost) RemoveStreamHandler(pid protocol.ID) {
	bh.Mux().RemoveHandler(string(pid))
	bh.emitters.evtLocalProtocolsUpdated.Emit(event.EvtLocalProtocolsUpdated{
		Removed: []protocol.ID{pid},
	})
}

func (bh *BlankHost) SetStreamHandler(pid protocol.ID, handler network.StreamHandler) {
	bh.Mux().AddHandler(string(pid), func(p string, rwc io.ReadWriteCloser) error {
		is := rwc.(network.Stream)
		is.SetProtocol(protocol.ID(p))
		handler(is)
		return nil
	})
	bh.emitters.evtLocalProtocolsUpdated.Emit(event.EvtLocalProtocolsUpdated{
		Added: []protocol.ID{pid},
	})
}

func (bh *BlankHost) SetStreamHandlerMatch(pid protocol.ID, m func(string) bool, handler network.StreamHandler) {
	bh.Mux().AddHandlerWithFunc(string(pid), m, func(p string, rwc io.ReadWriteCloser) error {
		is := rwc.(network.Stream)
		is.SetProtocol(protocol.ID(p))
		handler(is)
		return nil
	})
	bh.emitters.evtLocalProtocolsUpdated.Emit(event.EvtLocalProtocolsUpdated{
		Added: []protocol.ID{pid},
	})
}

// newStreamHandler is the remote-opened stream handler for network.Network
func (bh *BlankHost) newStreamHandler(s network.Stream) {
	protoID, handle, err := bh.Mux().Negotiate(s)
	if err != nil {
		log.Infow("protocol negotiation failed", "error", err)
		s.Reset()
		return
	}

	s.SetProtocol(protocol.ID(protoID))

	go handle(protoID, s)
}

// TODO: i'm not sure this really needs to be here
func (bh *BlankHost) Mux() protocol.Switch {
	return bh.mux
}

// TODO: also not sure this fits... Might be better ways around this (leaky abstractions)
func (bh *BlankHost) Network() network.Network {
	return bh.n
}

func (bh *BlankHost) ConnManager() connmgr.ConnManager {
	return bh.cmgr
}

func (bh *BlankHost) EventBus() event.Bus {
	return bh.eventbus
}
<commit_msg>add a WithEventBus constructor option<commit_after>package blankhost

import (
	"context"
	"errors"
	"fmt"
	"io"

	"github.com/libp2p/go-libp2p-core/connmgr"
	"github.com/libp2p/go-libp2p-core/event"
	"github.com/libp2p/go-libp2p-core/host"
	"github.com/libp2p/go-libp2p-core/network"
	"github.com/libp2p/go-libp2p-core/peer"
	"github.com/libp2p/go-libp2p-core/peerstore"
	"github.com/libp2p/go-libp2p-core/protocol"
	"github.com/libp2p/go-libp2p-core/record"

	"github.com/libp2p/go-eventbus"

	logging "github.com/ipfs/go-log/v2"

	ma "github.com/multiformats/go-multiaddr"
	mstream "github.com/multiformats/go-multistream"
)

var log = logging.Logger("blankhost")

// BlankHost is the thinnest implementation of the host.Host interface
type BlankHost struct {
	n        network.Network
	mux      *mstream.MultistreamMuxer
	cmgr     connmgr.ConnManager
	eventbus event.Bus
	emitters struct {
		evtLocalProtocolsUpdated event.Emitter
	}
}

type config struct {
	cmgr     connmgr.ConnManager
	eventBus event.Bus
}

type Option = func(cfg *config)

func WithConnectionManager(cmgr connmgr.ConnManager) Option {
	return func(cfg *config) {
		cfg.cmgr = cmgr
	}
}

func WithEventBus(eventBus event.Bus) Option {
	return func(cfg *config) {
		cfg.eventBus = eventBus
	}
}

func NewBlankHost(n network.Network, options ...Option) *BlankHost {
	cfg := config{
		cmgr: &connmgr.NullConnMgr{},
	}
	for _, opt := range options {
		opt(&cfg)
	}

	bh := &BlankHost{
		n:    n,
		cmgr: cfg.cmgr,
		mux:  mstream.NewMultistreamMuxer(),
	}
	if bh.eventbus == nil {
		bh.eventbus = eventbus.NewBus()
	}

	// subscribe the connection manager to network notifications (has no effect with NullConnMgr)
	n.Notify(bh.cmgr.Notifee())

	var err error
	if bh.emitters.evtLocalProtocolsUpdated, err = bh.eventbus.Emitter(&event.EvtLocalProtocolsUpdated{}); err != nil {
		return nil
	}
	evtPeerConnectednessChanged, err := bh.eventbus.Emitter(&event.EvtPeerConnectednessChanged{})
	if err != nil {
		return nil
	}
	n.Notify(newPeerConnectWatcher(evtPeerConnectednessChanged))

	n.SetStreamHandler(bh.newStreamHandler)

	// persist a signed peer record for self to the peerstore.
	if err := bh.initSignedRecord(); err != nil {
		log.Errorf("error creating blank host, err=%s", err)
		return nil
	}

	return bh
}

func (bh *BlankHost) initSignedRecord() error {
	cab, ok := peerstore.GetCertifiedAddrBook(bh.n.Peerstore())
	if !ok {
		log.Error("peerstore does not support signed records")
		return errors.New("peerstore does not support signed records")
	}
	rec := peer.PeerRecordFromAddrInfo(peer.AddrInfo{ID: bh.ID(), Addrs: bh.Addrs()})
	ev, err := record.Seal(rec, bh.Peerstore().PrivKey(bh.ID()))
	if err != nil {
		log.Errorf("failed to create signed record for self, err=%s", err)
		return fmt.Errorf("failed to create signed record for self, err=%s", err)
	}
	_, err = cab.ConsumePeerRecord(ev, peerstore.PermanentAddrTTL)
	if err != nil {
		log.Errorf("failed to persist signed record to peerstore,err=%s", err)
		return fmt.Errorf("failed to persist signed record for self, err=%s", err)
	}
	return err
}

var _ host.Host = (*BlankHost)(nil)

func (bh *BlankHost) Addrs() []ma.Multiaddr {
	addrs, err := bh.n.InterfaceListenAddresses()
	if err != nil {
		log.Debug("error retrieving network interface addrs: ", err)
		return nil
	}

	return addrs
}

func (bh *BlankHost) Close() error {
	return bh.n.Close()
}

func (bh *BlankHost) Connect(ctx context.Context, ai peer.AddrInfo) error {
	// absorb addresses into peerstore
	bh.Peerstore().AddAddrs(ai.ID, ai.Addrs, peerstore.TempAddrTTL)

	cs := bh.n.ConnsToPeer(ai.ID)
	if len(cs) > 0 {
		return nil
	}

	_, err := bh.Network().DialPeer(ctx, ai.ID)
	return err
}

func (bh *BlankHost) Peerstore() peerstore.Peerstore {
	return bh.n.Peerstore()
}

func (bh *BlankHost) ID() peer.ID {
	return bh.n.LocalPeer()
}

func (bh *BlankHost) NewStream(ctx context.Context, p peer.ID, protos ...protocol.ID) (network.Stream, error) {
	s, err := bh.n.NewStream(ctx, p)
	if err != nil {
		return nil, err
	}

	var protoStrs []string
	for _, pid := range protos {
		protoStrs = append(protoStrs, string(pid))
	}

	selected, err := mstream.SelectOneOf(protoStrs, s)
	if err != nil {
		s.Reset()
		return nil, err
	}

	selpid := protocol.ID(selected)
	s.SetProtocol(selpid)
	bh.Peerstore().AddProtocols(p, selected)

	return s, nil
}

func (bh *BlankHost) RemoveStreamHandler(pid protocol.ID) {
	bh.Mux().RemoveHandler(string(pid))
	bh.emitters.evtLocalProtocolsUpdated.Emit(event.EvtLocalProtocolsUpdated{
		Removed: []protocol.ID{pid},
	})
}

func (bh *BlankHost) SetStreamHandler(pid protocol.ID, handler network.StreamHandler) {
	bh.Mux().AddHandler(string(pid), func(p string, rwc io.ReadWriteCloser) error {
		is := rwc.(network.Stream)
		is.SetProtocol(protocol.ID(p))
		handler(is)
		return nil
	})
	bh.emitters.evtLocalProtocolsUpdated.Emit(event.EvtLocalProtocolsUpdated{
		Added: []protocol.ID{pid},
	})
}

func (bh *BlankHost) SetStreamHandlerMatch(pid protocol.ID, m func(string) bool, handler network.StreamHandler) {
	bh.Mux().AddHandlerWithFunc(string(pid), m, func(p string, rwc io.ReadWriteCloser) error {
		is := rwc.(network.Stream)
		is.SetProtocol(protocol.ID(p))
		handler(is)
		return nil
	})
	bh.emitters.evtLocalProtocolsUpdated.Emit(event.EvtLocalProtocolsUpdated{
		Added: []protocol.ID{pid},
	})
}

// newStreamHandler is the remote-opened stream handler for network.Network
func (bh *BlankHost) newStreamHandler(s network.Stream) {
	protoID, handle, err := bh.Mux().Negotiate(s)
	if err != nil {
		log.Infow("protocol negotiation failed", "error", err)
		s.Reset()
		return
	}

	s.SetProtocol(protocol.ID(protoID))

	go handle(protoID, s)
}

// TODO: i'm not sure this really needs to be here
func (bh *BlankHost) Mux() protocol.Switch {
	return bh.mux
}

// TODO: also not sure this fits... Might be better ways around this (leaky abstractions)
func (bh *BlankHost) Network() network.Network {
	return bh.n
}

func (bh *BlankHost) ConnManager() connmgr.ConnManager {
	return bh.cmgr
}

func (bh *BlankHost) EventBus() event.Bus {
	return bh.eventbus
}
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/go-martini/martini"
	_ "github.com/lib/pq"
	"github.com/martini-contrib/gzip"
)

func GetHostname() string {
	hostname := os.Getenv("DECKBREW_HOSTNAME")
	if hostname == "" {
		return "http://localhost:3000"
	}
	return hostname
}

func ReverseCard(id string) string {
	return fmt.Sprintf("%s/mtg/cards/%s", GetHostname(), id)
}

func ReverseEdition(id int) string {
	return fmt.Sprintf("%s/mtg/cards?multiverseid=%d", GetHostname(), id)
}

func ReverseSet(id string) string {
	return fmt.Sprintf("%s/mtg/sets/%s", GetHostname(), id)
}

func MTGImageURL(id int) string {
	return fmt.Sprintf("https://image.deckbrew.com/mtg/multiverseid/%d.jpg", id)
}

func Slug(name string) string {
	re := regexp.MustCompile(`[,.'"?:()]`)
	d := strings.Replace(strings.ToLower(name), " ", "-", -1)
	return re.ReplaceAllLiteralString(d, "")
}

// Import this eventually
type Card struct {
	Name          string            `json:"name"`
	Id            string            `json:"id"`
	Href          string            `json:"url,omitempty"`
	StoreUrl      string            `json:"store_url"`
	Types         []string          `json:"types,omitempty"`
	Supertypes    []string          `json:"supertypes,omitempty"`
	Subtypes      []string          `json:"subtypes,omitempty"`
	Colors        []string          `json:"colors,omitempty"`
	ConvertedCost int               `json:"cmc"`
	ManaCost      string            `json:"cost"`
	Text          string            `json:"text"`
	Power         string            `json:"power,omitempty"`
	Toughness     string            `json:"toughness,omitempty"`
	Loyalty       int               `json:"loyalty,omitempty"`
	FormatMap     map[string]string `json:"formats"`
	Editions      []Edition         `json:"editions,omitempty"`
}

func (c *Card) Sets() []string {
	sets := []string{}
	for _, e := range c.Editions {
		sets = append(sets, e.SetId)
	}
	return ToUniqueLower(sets)
}

func (c *Card) Formats() []string {
	v := []string{}
	for format, status := range c.FormatMap {
		if status == "legal" || status == "restricted" {
			v = append(v, format)
		}
	}
	return ToUniqueLower(v)
}

func (c *Card) Status() []string {
	v := []string{}
	for _, status := range c.FormatMap {
		v = append(v, status)
	}
	return ToUniqueLower(v)
}

func (c *Card) Rarities() []string {
	r := []string{}
	for _, e := range c.Editions {
		r = append(r, e.Rarity)
	}
	return ToUniqueLower(r)
}

func (c *Card) MultiverseIds() []string {
	r := []string{}
	for _, e := range c.Editions {
		r = append(r, strconv.Itoa(e.MultiverseId))
	}
	return ToUniqueLower(r)
}

func (c *Card) Multicolor() bool {
	return len(c.Colors) > 1
}

func (c *Card) Fill(pl *PriceList) {
	c.Href = ReverseCard(c.Id)
	c.StoreUrl = TCGCardURL(c)

	for i, _ := range c.Editions {
		e := &c.Editions[i]
		e.Href = ReverseEdition(e.MultiverseId)
		e.SetUrl = ReverseSet(e.SetId)
		e.ImageUrl = MTGImageURL(e.MultiverseId)
		e.StoreUrl = TCGEditionURL(c, e)
		e.Price = pl.GetPrice(e.MultiverseId)
	}
}

type Edition struct {
	Set          string `json:"set"`
	SetId        string `json:"set_id"`
	CardId       string `json:"-"`
	Watermark    string `json:"watermark,omitempty"`
	Rarity       string `json:"rarity"`
	Border       string `json:"-"`
	Artist       string `json:"artist"`
	MultiverseId int    `json:"multiverse_id"`
	Flavor       string `json:"flavor,omitempty"`
	Number       string `json:"number"`
	Layout       string `json:"layout"`
	Price        *Price `json:"price,omitempty"`
	Href         string `json:"url,omitempty"`
	ImageUrl     string `json:"image_url,omitempty"`
	SetUrl       string `json:"set_url,omitempty"`
	StoreUrl     string `json:"store_url"`
}

type Price struct {
	Low     int `json:"low"`
	Average int `json:"median"`
	High    int `json:"high"`
}

type Set struct {
	Id         string `json:"id"`
	Name       string `json:"name"`
	Border     string `json:"border"`
	Type       string `json:"type"`
	Href       string `json:"url"`
	CardsUrl   string `json:"cards_url"`
	PriceGuide string `json:"-"`
	Priced     bool   `json:"-"`
}

func (s *Set) Fill() {
	s.Href = fmt.Sprintf("%s/mtg/sets/%s", GetHostname(), s.Id)
	s.CardsUrl = fmt.Sprintf("%s/mtg/cards?set=%s", GetHostname(), s.Id)
}

func JSON(code int, val interface{}) (int, []byte) {
	blob, err := json.MarshalIndent(val, "", "  ")

	if err != nil {
		return 500, []byte(`{"error": "Internal server error :("}"`)
	}

	return code, blob
}

func Errors(errors ...string) ApiError {
	return ApiError{Errors: errors}
}

func LinkHeader(host string, u *url.URL, page int) string {
	if page == 0 {
		qstring := u.Query()
		qstring.Set("page", "1")
		return fmt.Sprintf("<%s%s?%s>; rel=\"next\"", host, u.Path, qstring.Encode())
	} else {
		qstring := u.Query()

		qstring.Set("page", strconv.Itoa(page-1))
		prev := fmt.Sprintf("<%s%s?%s>; rel=\"prev\"", host, u.Path, qstring.Encode())

		qstring.Set("page", strconv.Itoa(page+1))
		next := fmt.Sprintf("<%s%s?%s>; rel=\"next\"", host, u.Path, qstring.Encode())

		return prev + ", " + next
	}
}

type ApiError struct {
	Errors []string `json:"errors"`
}

func HandleCards(db *sql.DB, m *sync.RWMutex, pl *PriceList, req *http.Request, w http.ResponseWriter) (int, []byte) {
	cond, err, errors := ParseSearch(req.URL)
	if err != nil {
		log.Println(err)
		return JSON(http.StatusBadRequest, Errors(errors...))
	}
	page, err := CardsPaging(req.URL)
	if err != nil {
		log.Println(err)
		return JSON(http.StatusBadRequest, Errors(errors...))
	}
	cards, err := FetchCards(db, cond, page)
	if err != nil {
		log.Println(err)
		return JSON(http.StatusNotFound, Errors("Cards not found"))
	}

	m.RLock()
	for i, _ := range cards {
		cards[i].Fill(pl)
	}
	m.RUnlock()

	w.Header().Set("Link", LinkHeader(GetHostname(), req.URL, page))
	return JSON(http.StatusOK, cards)
}

func HandleRandomCard(db *sql.DB, w http.ResponseWriter, r *http.Request) (int, []byte) {
	var card string
	err := db.QueryRow("SELECT id FROM cards ORDER BY RANDOM() LIMIT 1").Scan(&card)
	switch {
	case err == sql.ErrNoRows:
		return JSON(http.StatusNotFound, Errors("No random card can be found"))
	case err != nil:
		log.Println(err)
		return JSON(http.StatusInternalServerError, Errors("Can't connect to database"))
	default:
		http.Redirect(w, r, "/mtg/cards/"+card, http.StatusFound)
		return JSON(http.StatusFound, []string{"Redirecting to /mtg/cards/" + card})
	}
}

func HandleCard(db *sql.DB, m *sync.RWMutex, pl *PriceList, params martini.Params) (int, []byte) {
	card, err := FetchCard(db, params["id"])
	if err != nil {
		log.Println(err)
		return JSON(http.StatusNotFound, Errors("Card not found"))
	}

	m.RLock()
	card.Fill(pl)
	m.RUnlock()

	return JSON(http.StatusOK, card)
}

func HandleSets(db *sql.DB) (int, []byte) {
	sets, err := FetchSets(db)
	if err != nil {
		log.Println(err)
		return JSON(http.StatusNotFound, Errors("Sets not found"))
	}
	return JSON(http.StatusOK, sets)
}

func HandleSet(db *sql.DB, params martini.Params) (int, []byte) {
	card, err := FetchSet(db, params["id"])

	if err != nil {
		log.Println(err)
		return JSON(http.StatusNotFound, Errors("Set not found"))
	}

	return JSON(http.StatusOK, card)
}

func HandleTerm(term string) func(*sql.DB) (int, []byte) {
	return func(db *sql.DB) (int, []byte) {
		terms, err := FetchTerms(db, term)
		if err != nil {
			log.Println(err)
			return JSON(http.StatusNotFound, Errors(term+" not found"))
		}
		return JSON(http.StatusOK, terms)
	}
}

func HandleTypeahead(db *sql.DB, m *sync.RWMutex, pl *PriceList, req *http.Request) (int, []byte) {
	cards, err := FetchTypeahead(db, req.URL.Query().Get("q"))
	if err != nil {
		log.Println(err)
		return JSON(http.StatusNotFound, Errors(" Can't find any cards that match that search"))
	}

	m.RLock()
	for i, _ := range cards {
		cards[i].Fill(pl)
	}
	m.RUnlock()

	return JSON(http.StatusOK, cards)
}

type Pong struct {
	Rally string `json:"rally"`
}

func Ping(db *sql.DB) (int, []byte) {
	if db.Ping() != nil {
		return JSON(http.StatusInternalServerError, Errors("The database could not be reached"))
	}
	return JSON(http.StatusOK, Pong{Rally: "serve"})
}

func NotFound() (int, []byte) {
	return JSON(http.StatusNotFound, Errors("No endpoint here"))
}

// Logger returns a middleware handler that logs the request as it goes in and the response as it goes out.
func Logger() martini.Handler {
	return func(res http.ResponseWriter, req *http.Request, c martini.Context, log *log.Logger) {
		start := time.Now()
		rw := res.(martini.ResponseWriter)
		c.Next()
		log.Printf("method=%s url=%s status=%v dt=%s\n", req.Method, req.URL.Path, rw.Status(), time.Since(start))
	}
}

func NewApi() *martini.Martini {
	m := martini.New()

	// Setup middleware
	m.Use(martini.Recovery())
	m.Use(Logger())
	m.Use(gzip.All())
	m.Use(func(w http.ResponseWriter, req *http.Request) {
		if req.URL.Path != "/mtg/cards/random" {
			w.Header().Set("Cache-Control", "public,max-age=3600")
		}
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Expose-Headers", "link,content-length")
		w.Header().Set("License", "The textual information presented through this API about Magic: The Gathering is copyrighted by Wizards of the Coast.")
		w.Header().Set("Disclaimer", "This API is not produced, endorsed, supported, or affiliated with Wizards of the Coast.")
		w.Header().Set("Pricing", "store.tcgplayer.com allows you to buy cards from any of our vendors, all at the same time, in a simple checkout experience. Shop, Compare & Save with TCGplayer.com!")
		w.Header().Set("Strict-Transport-Security", "max-age=86400")
	})

	r := martini.NewRouter()

	r.Get("/mtg/cards", HandleCards)
	r.Get("/mtg/cards/typeahead", HandleTypeahead)
	r.Get("/mtg/cards/random", HandleRandomCard)
	r.Get("/mtg/cards/:id", HandleCard)
	r.Get("/mtg/sets", HandleSets)
	r.Get("/mtg/sets/:id", HandleSet)
	r.Get("/mtg/colors", HandleTerm("colors"))
	r.Get("/mtg/supertypes", HandleTerm("supertypes"))
	r.Get("/mtg/subtypes", HandleTerm("subtypes"))
	r.Get("/mtg/types", HandleTerm("types"))
	r.NotFound(NotFound)

	m.Action(r.Handle)
	return m
}

func updatePrices(db *sql.DB, m *sync.RWMutex, pl *PriceList) {
	for {
		log.Println("Fetching new prices")
		prices, err := FetchPrices(db)
		if err != nil {
			log.Println(err)
		}

		m.Lock()
		pl.Prices = prices
		m.Unlock()

		time.Sleep(5 * time.Minute)
	}
}

func ServeWebsite() error {
	db, err := getDatabase()
	if err != nil {
		return err
	}
	prices, err := FetchPrices(db)
	if err != nil {
		return err
	}
	pricelist := PriceList{}
	pricelist.Prices = prices

	mutex := sync.RWMutex{}

	m := NewApi()
	m.Map(db)
	m.Map(&pricelist)
	m.Map(&mutex)

	go updatePrices(db, &mutex, &pricelist)

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	m.RunOnAddr(":" + port)
	return nil
}
<commit_msg>FUCK TCGPLAYER<commit_after>package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/go-martini/martini"
	_ "github.com/lib/pq"
	"github.com/martini-contrib/gzip"
)

func GetHostname() string {
	hostname := os.Getenv("DECKBREW_HOSTNAME")
	if hostname == "" {
		return "http://localhost:3000"
	}
	return hostname
}

func ReverseCard(id string) string {
	return fmt.Sprintf("%s/mtg/cards/%s", GetHostname(), id)
}

func ReverseEdition(id int) string {
	return fmt.Sprintf("%s/mtg/cards?multiverseid=%d", GetHostname(), id)
}

func ReverseSet(id string) string {
	return fmt.Sprintf("%s/mtg/sets/%s", GetHostname(), id)
}

func MTGImageURL(id int) string {
	return fmt.Sprintf("https://image.deckbrew.com/mtg/multiverseid/%d.jpg", id)
}

func Slug(name string) string {
	re := regexp.MustCompile(`[,.'"?:()]`)
	d := strings.Replace(strings.ToLower(name), " ", "-", -1)
	return re.ReplaceAllLiteralString(d, "")
}

// Import this eventually
type Card struct {
	Name          string            `json:"name"`
	Id            string            `json:"id"`
	Href          string            `json:"url,omitempty"`
	StoreUrl      string            `json:"store_url"`
	Types         []string          `json:"types,omitempty"`
	Supertypes    []string          `json:"supertypes,omitempty"`
	Subtypes      []string          `json:"subtypes,omitempty"`
	Colors        []string          `json:"colors,omitempty"`
	ConvertedCost int               `json:"cmc"`
	ManaCost      string            `json:"cost"`
	Text          string            `json:"text"`
	Power         string            `json:"power,omitempty"`
	Toughness     string            `json:"toughness,omitempty"`
	Loyalty       int               `json:"loyalty,omitempty"`
	FormatMap     map[string]string `json:"formats"`
	Editions      []Edition         `json:"editions,omitempty"`
}

func (c *Card) Sets() []string {
	sets := []string{}
	for _, e := range c.Editions {
		sets = append(sets, e.SetId)
	}
	return ToUniqueLower(sets)
}

func (c *Card) Formats() []string {
	v := []string{}
	for format, status := range c.FormatMap {
		if status == "legal" || status == "restricted" {
			v = append(v, format)
		}
	}
	return ToUniqueLower(v)
}

func (c *Card) Status() []string {
	v := []string{}
	for _, status := range c.FormatMap {
		v = append(v, status)
	}
	return ToUniqueLower(v)
}

func (c *Card) Rarities() []string {
	r := []string{}
	for _, e := range c.Editions {
		r = append(r, e.Rarity)
	}
	return ToUniqueLower(r)
}

func (c *Card) MultiverseIds() []string {
	r := []string{}
	for _, e := range c.Editions {
		r = append(r, strconv.Itoa(e.MultiverseId))
	}
	return ToUniqueLower(r)
}

func (c *Card) Multicolor() bool {
	return len(c.Colors) > 1
}

func (c *Card) Fill() {
	c.Href = ReverseCard(c.Id)
	c.StoreUrl = TCGCardURL(c)

	for i, _ := range c.Editions {
		e := &c.Editions[i]
		e.Href = ReverseEdition(e.MultiverseId)
		e.SetUrl = ReverseSet(e.SetId)
		e.ImageUrl = MTGImageURL(e.MultiverseId)
		e.StoreUrl = TCGEditionURL(c, e)
		e.Price = &Price{
			Low:     0,
			Average: 0,
			High:    0,
		}
	}
}

type Edition struct {
	Set          string `json:"set"`
	SetId        string `json:"set_id"`
	CardId       string `json:"-"`
	Watermark    string `json:"watermark,omitempty"`
	Rarity       string `json:"rarity"`
	Border       string `json:"-"`
	Artist       string `json:"artist"`
	MultiverseId int    `json:"multiverse_id"`
	Flavor       string `json:"flavor,omitempty"`
	Number       string `json:"number"`
	Layout       string `json:"layout"`
	Price        *Price `json:"price,omitempty"`
	Href         string `json:"url,omitempty"`
	ImageUrl     string `json:"image_url,omitempty"`
	SetUrl       string `json:"set_url,omitempty"`
	StoreUrl     string `json:"store_url"`
}

type Price struct {
	Low     int `json:"low"`
	Average int `json:"median"`
	High    int `json:"high"`
}

type Set struct {
	Id         string `json:"id"`
	Name       string `json:"name"`
	Border     string `json:"border"`
	Type       string `json:"type"`
	Href       string `json:"url"`
	CardsUrl   string `json:"cards_url"`
	PriceGuide string `json:"-"`
	Priced     bool   `json:"-"`
}

func (s *Set) Fill() {
	s.Href = fmt.Sprintf("%s/mtg/sets/%s", GetHostname(), s.Id)
	s.CardsUrl = fmt.Sprintf("%s/mtg/cards?set=%s", GetHostname(), s.Id)
}

func JSON(code int, val interface{}) (int, []byte) {
	blob, err := json.MarshalIndent(val, "", "  ")

	if err != nil {
		return 500, []byte(`{"error": "Internal server error :("}"`)
	}

	return code, blob
}

func Errors(errors ...string) ApiError {
	return ApiError{Errors: errors}
}

func LinkHeader(host string, u *url.URL, page int) string {
	if page == 0 {
		qstring := u.Query()
		qstring.Set("page", "1")
		return fmt.Sprintf("<%s%s?%s>; rel=\"next\"", host, u.Path, qstring.Encode())
	} else {
		qstring := u.Query()

		qstring.Set("page", strconv.Itoa(page-1))
		prev := fmt.Sprintf("<%s%s?%s>; rel=\"prev\"", host, u.Path, qstring.Encode())

		qstring.Set("page", strconv.Itoa(page+1))
		next := fmt.Sprintf("<%s%s?%s>; rel=\"next\"", host, u.Path, qstring.Encode())

		return prev + ", " + next
	}
}

type ApiError struct {
	Errors []string `json:"errors"`
}

func HandleCards(db *sql.DB, req *http.Request, w http.ResponseWriter) (int, []byte) {
	cond, err, errors := ParseSearch(req.URL)
	if err != nil {
		log.Println(err)
		return JSON(http.StatusBadRequest, Errors(errors...))
	}
	page, err := CardsPaging(req.URL)
	if err != nil {
		log.Println(err)
		return JSON(http.StatusBadRequest, Errors(errors...))
	}
	cards, err := FetchCards(db, cond, page)
	if err != nil {
		log.Println(err)
		return JSON(http.StatusNotFound, Errors("Cards not found"))
	}

	for i, _ := range cards {
		cards[i].Fill()
	}

	w.Header().Set("Link", LinkHeader(GetHostname(), req.URL, page))
	return JSON(http.StatusOK, cards)
}

func HandleRandomCard(db *sql.DB, w http.ResponseWriter, r *http.Request) (int, []byte) {
	var card string
	err := db.QueryRow("SELECT id FROM cards ORDER BY RANDOM() LIMIT 1").Scan(&card)
	switch {
	case err == sql.ErrNoRows:
		return JSON(http.StatusNotFound, Errors("No random card can be found"))
	case err != nil:
		log.Println(err)
		return JSON(http.StatusInternalServerError, Errors("Can't connect to database"))
	default:
		http.Redirect(w, r, "/mtg/cards/"+card, http.StatusFound)
		return JSON(http.StatusFound, []string{"Redirecting to /mtg/cards/" + card})
	}
}

func HandleCard(db *sql.DB, params martini.Params) (int, []byte) {
	card, err := FetchCard(db, params["id"])
	if err != nil {
		log.Println(err)
		return JSON(http.StatusNotFound, Errors("Card not found"))
	}

	card.Fill()

	return JSON(http.StatusOK, card)
}

func HandleSets(db *sql.DB) (int, []byte) {
	sets, err := FetchSets(db)
	if err != nil {
		log.Println(err)
		return JSON(http.StatusNotFound, Errors("Sets not found"))
	}
	return JSON(http.StatusOK, sets)
}

func HandleSet(db *sql.DB, params martini.Params) (int, []byte) {
	card, err := FetchSet(db, params["id"])

	if err != nil {
		log.Println(err)
		return JSON(http.StatusNotFound, Errors("Set not found"))
	}

	return JSON(http.StatusOK, card)
}

func HandleTerm(term string) func(*sql.DB) (int, []byte) {
	return func(db *sql.DB) (int, []byte) {
		terms, err := FetchTerms(db, term)
		if err != nil {
			log.Println(err)
			return JSON(http.StatusNotFound, Errors(term+" not found"))
		}
		return JSON(http.StatusOK, terms)
	}
}

func HandleTypeahead(db *sql.DB, req *http.Request) (int, []byte) {
	cards, err := FetchTypeahead(db, req.URL.Query().Get("q"))
	if err != nil {
		log.Println(err)
		return JSON(http.StatusNotFound, Errors(" Can't find any cards that match that search"))
	}

	for i, _ := range cards {
		cards[i].Fill()
	}

	return JSON(http.StatusOK, cards)
}

type Pong struct {
	Rally string `json:"rally"`
}

func Ping(db *sql.DB) (int, []byte) {
	if db.Ping() != nil {
		return JSON(http.StatusInternalServerError, Errors("The database could not be reached"))
	}
	return JSON(http.StatusOK, Pong{Rally: "serve"})
}

func NotFound() (int, []byte) {
	return JSON(http.StatusNotFound, Errors("No endpoint here"))
}

// Logger returns a middleware handler that logs the request as it goes in and the response as it goes out.
func Logger() martini.Handler {
	return func(res http.ResponseWriter, req *http.Request, c martini.Context, log *log.Logger) {
		start := time.Now()
		rw := res.(martini.ResponseWriter)
		c.Next()
		log.Printf("method=%s url=%s status=%v dt=%s\n", req.Method, req.URL.Path, rw.Status(), time.Since(start))
	}
}

func NewApi() *martini.Martini {
	m := martini.New()

	// Setup middleware
	m.Use(martini.Recovery())
	m.Use(Logger())
	m.Use(gzip.All())
	m.Use(func(w http.ResponseWriter, req *http.Request) {
		if req.URL.Path != "/mtg/cards/random" {
			w.Header().Set("Cache-Control", "public,max-age=3600")
		}
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Expose-Headers", "link,content-length")
		w.Header().Set("License", "The textual information presented through this API about Magic: The Gathering is copyrighted by Wizards of the Coast.")
		w.Header().Set("Disclaimer", "This API is not produced, endorsed, supported, or affiliated with Wizards of the Coast.")
		w.Header().Set("Pricing", "store.tcgplayer.com allows you to buy cards from any of our vendors, all at the same time, in a simple checkout experience. Shop, Compare & Save with TCGplayer.com!")
		w.Header().Set("Strict-Transport-Security", "max-age=86400")
	})

	r := martini.NewRouter()

	r.Get("/mtg/cards", HandleCards)
	r.Get("/mtg/cards/typeahead", HandleTypeahead)
	r.Get("/mtg/cards/random", HandleRandomCard)
	r.Get("/mtg/cards/:id", HandleCard)
	r.Get("/mtg/sets", HandleSets)
	r.Get("/mtg/sets/:id", HandleSet)
	r.Get("/mtg/colors", HandleTerm("colors"))
	r.Get("/mtg/supertypes", HandleTerm("supertypes"))
	r.Get("/mtg/subtypes", HandleTerm("subtypes"))
	r.Get("/mtg/types", HandleTerm("types"))
	r.NotFound(NotFound)

	m.Action(r.Handle)
	return m
}

func ServeWebsite() error {
	db, err := getDatabase()
	if err != nil {
		return err
	}

	m := NewApi()
	m.Map(db)

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	m.RunOnAddr(":" + port)
	return nil
}
<|endoftext|>


<commit_before>package main

import (
    "github.com/go-martini/martini"
    "github.com/martini-contrib/render"
    "github.com/martini-contrib/binding"
    "github.com/Lupino/collect/models"
    "mime/multipart"
    "net/http"
)

type FileForm struct {
    File *multipart.FileHeader `form:"file" binding:"required"`
}

type TagForm struct {
    Tag  string                `form:"tag"  binding:"required"`
}

type DatasetForm struct {
    FileForm
    TagForm
}

func api(mart *martini.ClassicMartini) {
    mart.Post(API + "/dataset", binding.Bind(DatasetForm{}), func(form DatasetForm, r render.Render) {
        var err error
        var file *models.File
        var tag *models.Tag
        var dataset *models.Dataset

        if file, err = uploadFile(form.File); err != nil {
            r.JSON(http.StatusInternalServerError, map[string]interface{}{"err": err.Error()})
        }

        if tag, err = saveTag(form.Tag); err != nil {
            r.JSON(http.StatusInternalServerError, map[string]interface{}{"err": err.Error()})
        }

        if dataset, err = saveDataset(file, tag, 0); err != nil {
            r.JSON(http.StatusInternalServerError, map[string]interface{}{"err": err.Error()})
        }

        r.JSON(http.StatusOK, map[string]*models.Dataset{"dataset": dataset})
    })
}
<commit_msg>rename api<commit_after>package main

import (
    "github.com/go-martini/martini"
    "github.com/martini-contrib/render"
    "github.com/martini-contrib/binding"
    "github.com/Lupino/collect/models"
    "mime/multipart"
    "net/http"
)

type FileForm struct {
    File *multipart.FileHeader `form:"file" binding:"required"`
}

type TagForm struct {
    Tag  string                `form:"tag"  binding:"required"`
}

type DatasetForm struct {
    FileForm
    TagForm
}

func api(mart *martini.ClassicMartini) {
    mart.Post(API + "/datasets", binding.Bind(DatasetForm{}), func(form DatasetForm, r render.Render) {
        var err error
        var file *models.File
        var tag *models.Tag
        var dataset *models.Dataset

        if file, err = uploadFile(form.File); err != nil {
            r.JSON(http.StatusInternalServerError, map[string]interface{}{"err": err.Error()})
        }

        if tag, err = saveTag(form.Tag); err != nil {
            r.JSON(http.StatusInternalServerError, map[string]interface{}{"err": err.Error()})
        }

        if dataset, err = saveDataset(file, tag, 0); err != nil {
            r.JSON(http.StatusInternalServerError, map[string]interface{}{"err": err.Error()})
        }

        r.JSON(http.StatusOK, map[string]*models.Dataset{"dataset": dataset})
    })
}
<|endoftext|>


<commit_before>package api

import (
	"bytes"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"

	"encoding/json"
	"encoding/xml"
)

const (
	POST   = "POST"
	GET    = "GET"
	HEAD   = "HEAD"
	PUT    = "PUT"
	DELETE = "DELETE"
	PATCH  = "PATCH"
)

var types = map[string]string{
	"html":       "text/html",
	"json":       "application/json",
	"xml":        "application/xml",
	"text":       "text/plain",
	"urlencoded": "application/x-www-form-urlencoded",
	"form":       "application/x-www-form-urlencoded",
	"form-data":  "application/x-www-form-urlencoded",
}

type Agent struct {
	u       *url.URL
	t       string
	m       string
	heads   map[string]string
	query   url.Values
	cookies []*http.Cookie
	data    io.Reader
	length  int
	Error   error
	debug   bool
	conn    *http.Client
}

func URL(aurl string) *Agent {
	u, err := url.Parse(aurl)
	return &Agent{
		u:       u,
		t:       types["html"],
		m:       GET,
		heads:   make(map[string]string),
		query:   url.Values{},
		cookies: make([]*http.Cookie, 0),
		Error:   err,
		conn:    http.DefaultClient,
	}
}

func Get(aurl string) *Agent {
	return URL(aurl).Method(GET)
}

func Post(aurl string) *Agent {
	return URL(aurl).Method(POST)
}

func Patch(aurl string) *Agent {
	return URL(aurl).Method(PATCH)
}

func Put(aurl string) *Agent {
	return URL(aurl).Method(PUT)
}

func Head(aurl string) *Agent {
	return URL(aurl).Method(HEAD)
}

func HTTP(host string) *Agent {
	return &Agent{
		u: &url.URL{
			Scheme: "http",
			Host:   host,
		},
		t:       types["html"],
		m:       GET,
		heads:   make(map[string]string),
		query:   url.Values{},
		cookies: make([]*http.Cookie, 0),
	}
}

func HTTPs(host string) *Agent {
	return &Agent{
		u: &url.URL{
			Scheme: "https",
			Host:   host,
		},
		t:       types["html"],
		m:       GET,
		heads:   make(map[string]string),
		query:   url.Values{},
		cookies: make([]*http.Cookie, 0),
	}
}

func (a *Agent) Debug(flag bool) *Agent {
	a.debug = flag
	return a
}
func (a *Agent) URI(uri string) *Agent {
	a.u.Path = uri
	return a
}

func (a *Agent) QueryGet() url.Values {
	q := a.u.Query()
	for k, v := range a.query {
		for _, vv := range v {
			q.Add(k, vv)
		}
	}
	return q
}

func (a *Agent) QuerySet(key string, value string) *Agent {
	a.query.Set(key, value)
	return a
}

func (a *Agent) QueryAdd(key string, value string) *Agent {
	a.query.Add(key, value)
	return a
}

func (a *Agent) QueryDel(key string) *Agent {
	a.query.Del(key)
	return a
}

func (a *Agent) HeadSet(key string, value string) *Agent {
	a.heads[key] = value
	return a
}

func (a *Agent) HeadDel(key string) *Agent {
	delete(a.heads, key)
	return a
}

func (a *Agent) BasicAuthSet(user, password string) *Agent {
	a.u.User = url.UserPassword(user, password)
	return a
}

func (a *Agent) BasicAuthDel() *Agent {
	a.u.User = nil
	return a
}

func (a *Agent) CookiesAdd(cookies ...*http.Cookie) *Agent {
	a.cookies = append(a.cookies, cookies...)
	return a
}

func (a *Agent) Method(m string) *Agent {
	a.m = m
	return a
}

func (a *Agent) ContentType(t string) *Agent {
	a.m = types[t]
	return a
}

func (a *Agent) FormData(form map[string][]string) *Agent {
	data := url.Values(form).Encode()
	a.data = strings.NewReader(data)
	a.length = len(data)
	return a
}

func JSONMarshal(v interface{}, unescape bool) ([]byte, error) {
	b, err := json.Marshal(v)

	if unescape {
		b = bytes.Replace(b, []byte("\\u003c"), []byte("<"), -1)
		b = bytes.Replace(b, []byte("\\u003e"), []byte(">"), -1)
		b = bytes.Replace(b, []byte("\\u0026"), []byte("&"), -1)
	}
	return b, err
}

func (a *Agent) JSONData(args ...interface{}) *Agent {
	if len(args) == 1 {
		data, err := JSONMarshal(args[0], false)
		a.data = bytes.NewBuffer(data)
		a.length = len(data)
		a.Error = err
	}

	if len(args) == 2 {
		data, err := JSONMarshal(args[0], args[1].(bool))
		a.data = bytes.NewBuffer(data)
		a.length = len(data)
		a.Error = err
	}
	return a
}

func (a *Agent) XMLData(obj interface{}) *Agent {
	data, err := xml.Marshal(obj)
	a.data = bytes.NewBuffer(data)
	a.length = len(data)
	a.Error = err
	return a
}

func (a *Agent) Bytes() (*http.Response, []byte, error) {
	req, err := http.NewRequest(a.m, a.u.String(), a.data)
	if err != nil {
		a.Error = err
		return nil, nil, err
	}

	//! headers
	req.Header.Set("Content-Type", types[a.t])
	for k, v := range a.heads {
		req.Header.Set(k, v)
	}

	//! query
	q := req.URL.Query()
	for k, v := range a.query {
		for _, vv := range v {
			q.Add(k, vv)
		}
	}
	req.URL.RawQuery = q.Encode()

	//! basic auth
	if a.u.User != nil {
		if password, ok := a.u.User.Password(); ok {
			req.SetBasicAuth(a.u.User.Username(), password)
		}
	}

	//! cookies
	for _, cookie := range a.cookies {
		req.AddCookie(cookie)
	}

	//! do
	if a.debug {
		dump, _ := httputil.DumpRequest(req, true)
		log.Printf("api request\n-------------------------------\n%s\n", string(dump))
	}

	resp, err := a.conn.Do(req)
	if err != nil {
		a.Error = err
		return nil, nil, err
	}
	defer resp.Body.Close()

	if a.debug {
		dump, _ := httputil.DumpResponse(resp, true)
		log.Printf("api response\n--------------------------------\n%s\n", string(dump))
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		a.Error = err
		return nil, nil, err
	}

	resp.Body = ioutil.NopCloser(bytes.NewBuffer(body))
	return resp, body, a.Error
}

func (a *Agent) Text() (*http.Response, string, error) {
	a.t = "text"

	resp, bytes, err := a.Bytes()
	return resp, string(bytes), err
}

func (a *Agent) JSON(obj interface{}) (*http.Response, []byte, error) {
	a.t = "json"

	resp, bytes, err := a.Bytes()
	if err != nil {
		return resp, bytes, err
	}

	//! decode bytes to json
	if obj != nil {
		if err := json.NewDecoder(resp.Body).Decode(&obj); err != nil {
			a.Error = err
			return resp, bytes, err
		}
	}

	return resp, bytes, a.Error
}

func (a *Agent) XML(obj interface{}) (*http.Response, []byte, error) {
	a.t = "xml"

	resp, bytes, err := a.Bytes()
	if err != nil {
		return resp, bytes, err
	}

	//! decode bytes to json
	if obj != nil {
		if err := xml.NewDecoder(resp.Body).Decode(&obj); err != nil {
			a.Error = err
			return resp, bytes, err
		}
	}

	return resp, bytes, a.Error
}
<commit_msg>add fragment<commit_after>package api

import (
	"bytes"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"

	"encoding/json"
	"encoding/xml"
)

const (
	POST   = "POST"
	GET    = "GET"
	HEAD   = "HEAD"
	PUT    = "PUT"
	DELETE = "DELETE"
	PATCH  = "PATCH"
)

var types = map[string]string{
	"html":       "text/html",
	"json":       "application/json",
	"xml":        "application/xml",
	"text":       "text/plain",
	"urlencoded": "application/x-www-form-urlencoded",
	"form":       "application/x-www-form-urlencoded",
	"form-data":  "application/x-www-form-urlencoded",
}

type Agent struct {
	u       *url.URL
	t       string
	m       string
	heads   map[string]string
	query   url.Values
	cookies []*http.Cookie
	data    io.Reader
	length  int
	Error   error
	debug   bool
	conn    *http.Client
}

func URL(aurl string) *Agent {
	u, err := url.Parse(aurl)
	return &Agent{
		u:       u,
		t:       types["html"],
		m:       GET,
		heads:   make(map[string]string),
		query:   url.Values{},
		cookies: make([]*http.Cookie, 0),
		Error:   err,
		conn:    http.DefaultClient,
	}
}

func Get(aurl string) *Agent {
	return URL(aurl).Method(GET)
}

func Post(aurl string) *Agent {
	return URL(aurl).Method(POST)
}

func Patch(aurl string) *Agent {
	return URL(aurl).Method(PATCH)
}

func Put(aurl string) *Agent {
	return URL(aurl).Method(PUT)
}

func Head(aurl string) *Agent {
	return URL(aurl).Method(HEAD)
}

func HTTP(host string) *Agent {
	return &Agent{
		u: &url.URL{
			Scheme: "http",
			Host:   host,
		},
		t:       types["html"],
		m:       GET,
		heads:   make(map[string]string),
		query:   url.Values{},
		cookies: make([]*http.Cookie, 0),
	}
}

func HTTPs(host string) *Agent {
	return &Agent{
		u: &url.URL{
			Scheme: "https",
			Host:   host,
		},
		t:       types["html"],
		m:       GET,
		heads:   make(map[string]string),
		query:   url.Values{},
		cookies: make([]*http.Cookie, 0),
	}
}

func (a *Agent) Debug(flag bool) *Agent {
	a.debug = flag
	return a
}
func (a *Agent) URI(uri string) *Agent {
	a.u.Path = uri
	return a
}

func (a *Agent) QueryGet() url.Values {
	q := a.u.Query()
	for k, v := range a.query {
		for _, vv := range v {
			q.Add(k, vv)
		}
	}
	return q
}

func (a *Agent) QuerySet(key string, value string) *Agent {
	a.query.Set(key, value)
	return a
}

func (a *Agent) QueryAdd(key string, value string) *Agent {
	a.query.Add(key, value)
	return a
}

func (a *Agent) Fragment(value string) *Agent {
	a.u.Fragment = value
	return a
}

func (a *Agent) QueryDel(key string) *Agent {
	a.query.Del(key)
	return a
}

func (a *Agent) HeadSet(key string, value string) *Agent {
	a.heads[key] = value
	return a
}

func (a *Agent) HeadDel(key string) *Agent {
	delete(a.heads, key)
	return a
}

func (a *Agent) BasicAuthSet(user, password string) *Agent {
	a.u.User = url.UserPassword(user, password)
	return a
}

func (a *Agent) BasicAuthDel() *Agent {
	a.u.User = nil
	return a
}

func (a *Agent) CookiesAdd(cookies ...*http.Cookie) *Agent {
	a.cookies = append(a.cookies, cookies...)
	return a
}

func (a *Agent) Method(m string) *Agent {
	a.m = m
	return a
}

func (a *Agent) ContentType(t string) *Agent {
	a.m = types[t]
	return a
}

func (a *Agent) FormData(form map[string][]string) *Agent {
	data := url.Values(form).Encode()
	a.data = strings.NewReader(data)
	a.length = len(data)
	return a
}

func JSONMarshal(v interface{}, unescape bool) ([]byte, error) {
	b, err := json.Marshal(v)

	if unescape {
		b = bytes.Replace(b, []byte("\\u003c"), []byte("<"), -1)
		b = bytes.Replace(b, []byte("\\u003e"), []byte(">"), -1)
		b = bytes.Replace(b, []byte("\\u0026"), []byte("&"), -1)
	}
	return b, err
}

func (a *Agent) JSONData(args ...interface{}) *Agent {
	if len(args) == 1 {
		data, err := JSONMarshal(args[0], false)
		a.data = bytes.NewBuffer(data)
		a.length = len(data)
		a.Error = err
	}

	if len(args) == 2 {
		data, err := JSONMarshal(args[0], args[1].(bool))
		a.data = bytes.NewBuffer(data)
		a.length = len(data)
		a.Error = err
	}
	return a
}

func (a *Agent) XMLData(obj interface{}) *Agent {
	data, err := xml.Marshal(obj)
	a.data = bytes.NewBuffer(data)
	a.length = len(data)
	a.Error = err
	return a
}

func (a *Agent) Bytes() (*http.Response, []byte, error) {
	req, err := http.NewRequest(a.m, a.u.String(), a.data)
	if err != nil {
		a.Error = err
		return nil, nil, err
	}

	//! headers
	req.Header.Set("Content-Type", types[a.t])
	for k, v := range a.heads {
		req.Header.Set(k, v)
	}

	//! query
	q := req.URL.Query()
	for k, v := range a.query {
		for _, vv := range v {
			q.Add(k, vv)
		}
	}
	req.URL.RawQuery = q.Encode()

	//! basic auth
	if a.u.User != nil {
		if password, ok := a.u.User.Password(); ok {
			req.SetBasicAuth(a.u.User.Username(), password)
		}
	}

	//! cookies
	for _, cookie := range a.cookies {
		req.AddCookie(cookie)
	}

	//! do
	if a.debug {
		dump, _ := httputil.DumpRequest(req, true)
		log.Printf("api request\n-------------------------------\n%s\n", string(dump))
	}

	resp, err := a.conn.Do(req)
	if err != nil {
		a.Error = err
		return nil, nil, err
	}
	defer resp.Body.Close()

	if a.debug {
		dump, _ := httputil.DumpResponse(resp, true)
		log.Printf("api response\n--------------------------------\n%s\n", string(dump))
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		a.Error = err
		return nil, nil, err
	}

	resp.Body = ioutil.NopCloser(bytes.NewBuffer(body))
	return resp, body, a.Error
}

func (a *Agent) Text() (*http.Response, string, error) {
	a.t = "text"

	resp, bytes, err := a.Bytes()
	return resp, string(bytes), err
}

func (a *Agent) JSON(obj interface{}) (*http.Response, []byte, error) {
	a.t = "json"

	resp, bytes, err := a.Bytes()
	if err != nil {
		return resp, bytes, err
	}

	//! decode bytes to json
	if obj != nil {
		if err := json.NewDecoder(resp.Body).Decode(&obj); err != nil {
			a.Error = err
			return resp, bytes, err
		}
	}

	return resp, bytes, a.Error
}

func (a *Agent) XML(obj interface{}) (*http.Response, []byte, error) {
	a.t = "xml"

	resp, bytes, err := a.Bytes()
	if err != nil {
		return resp, bytes, err
	}

	//! decode bytes to json
	if obj != nil {
		if err := xml.NewDecoder(resp.Body).Decode(&obj); err != nil {
			a.Error = err
			return resp, bytes, err
		}
	}

	return resp, bytes, a.Error
}
<|endoftext|>