<commit_before>package main

import (
	"os"
	"fmt"
	"log"
	"time"
	"strconv"
 	"database/sql"
	"github.com/coopernurse/gorp"
	_ "github.com/mattn/go-sqlite3"
	"github.com/aviddiviner/gin-limit"
	"github.com/gin-gonic/gin"
)

var (
	dbFilename  string = "./db/electronicArtArgentina.sqlite3"
	tableName   string = "exhibiciones"
	httpPort    string = "8080"
	apiHostname string = "http://arte-electronico.cyberpunk.com.ar"
	apiPath     string = "/api"
	perPage     int = 20
	firstPage   string = "1"
	lastPage    string = "80" // (1593 / perPage) // TODO FIX ME

)

var dbmap = initDb()

type (
	Exhibition struct {
		Id         int64 `db:"id" json:"-"`
		Anio       int `db:"anio" json:"year"`
		Nombre     string `db:"nombre_exhibicion" json:"exhibition"`
		Locacion   string `db:"nombre_locacion" json:"location"`
		Curador0   string `db:"curador_0" json:"curator_0,omitempty"`
		Curador1   string `db:"curador_1" json:"curator_1,omitempty"`
		Curador2   string `db:"curador_2" json:"curator_2,omitempty"`
		Curador3   string `db:"curador_3" json:"curator_3,omitempty"`
		FechaIncio time.Time `db:"fecha_inicio" json:"start"`
		FechaFin   time.Time `db:"fecha_finalizacion" json:"end"`
		Obra       string `db:"nombre_obra" json:"work,omitempty"`
		Artista0   string `db:"nombre_artista_0" json:"artist_0,omitempty"`
		Artista1   string `db:"nombre_artista_1" json:"artist_1,omitempty"`
		Artista2   string `db:"nombre_artista_2" json:"artist_2,omitempty"`
		Artista3   string `db:"nombre_artista_3" json:"artist_3,omitempty"`
		Artista4   string `db:"nombre_artista_4" json:"artist_3,omitempty"`
		Artista5   string `db:"nombre_artista_5" json:"artist_5,omitempty"`
		Artista6   string `db:"nombre_artista_6" json:"artist_6,omitempty"`
		Artista7   string `db:"nombre_artista_7" json:"artist_7,omitempty"`
		Artista8   string `db:"nombre_artista_8" json:"artist_8,omitempty"`
		Tecnica    string `db:"tecnica" json:"technique,omitempty"`
	}

	PaginationLinks struct {
		First       string `json:"first"`
		Last       string `json:"last"`
		Prev       string `json:"prev"`
		Next       string `json:"next"`
	}

	ResourceObject struct {
		Id         int64 `json:"id"`
		Type       string `json:"type"`
		Attributes Exhibition `json:"attributes"`
	}

	ResourceObjects struct {
		Data []ResourceObject `json:"data"`
		Links PaginationLinks `json:"links"`
	}

	Error struct {
		Id     string `json:"id"`
		Status int `json:"status"`
		Title  string `json:"title"`
		Detail string `json:"detail"`
	}

)

func paginationLink(endpoint string, pg string, which string) string {
	page, err := strconv.ParseInt(pg, 10, 32)
	checkErr(err)
	first, ferr := strconv.ParseInt(firstPage, 10, 32)
	checkErr(ferr)
	last, lerr := strconv.ParseInt(lastPage, 10, 32)
	checkErr(lerr)

	switch which {
	case "prev":
		if page > first {
			page = page - 1
		}
	case "next":
		if page <= last {
			page = page + 1
		}
	}

	link := fmt.Sprintf("%s/%s?page=%d", apiPath, endpoint, page)
	if endpoint == "search" && which == "last" { // TODO ugly
		link = ""
	}

	return link
}

func newResourceObject(attr Exhibition) ResourceObject {
    return ResourceObject {
        Id: attr.Id,
        Type: "exhibitions",
        Attributes: attr,
    }
}

func newResourceObjects (exhibitions []Exhibition, endpoint string, pg string) ResourceObjects {
	resources := make([]ResourceObject, 0)
	for e := range exhibitions {
		resources = append(resources, newResourceObject(exhibitions[e]))
	}

	links := PaginationLinks {
		First: paginationLink(endpoint, firstPage, "first"),
		Last: paginationLink(endpoint, lastPage, "last"),
		Prev: paginationLink(endpoint, pg, "prev"),
		Next: paginationLink(endpoint, pg, "next"),
	}

	return ResourceObjects{Data: resources, Links: links}
}

func pageOffset(pg string) int {
	page, err := strconv.Atoi(pg)
	if err != nil {
		page = 1
	}

	return perPage * (page - 1)
}

func PaginateQuery(query, pg string) string {
	return fmt.Sprintf("%s LIMIT %d OFFSET %d", query, perPage, pageOffset(pg))
}

func init() {
	p := os.Getenv("HTTP_PORT")
	if p != "" {
		httpPort = p
	}

	d := os.Getenv("DB_FILENAME")
	if d != "" {
		dbFilename = d
	}
}

func main() {
	router := gin.Default()
	router.Use(ApiCors())
	router.Use(limit.MaxAllowed(5))

	// API v1
	v1 := router.Group("api/v1")
	{
		v1.GET("/", ListEndpoints)  // list all exhibitions
		v1.GET("/exhibitions", GetExhibitions)  // list all exhibitions
		v1.GET("/exhibitions/:e_id", GetExhibition)  // show an exhibition
		v1.GET("/search", SearchExhibitions)  // search exhibitions

		v1.OPTIONS("/", EndpointsOptions) // http options
		v1.OPTIONS("/exhibitions", EndpointsOptions) // http options
		v1.OPTIONS("/exhibitions/:e_id", EndpointsOptions) // http options
		v1.OPTIONS("/search", EndpointsOptions) // http options
	}

	// API -- TODO improve this
	current := router.Group("api")
	{
		current.GET("/", ListEndpoints)  // list all exhibitions
		current.GET("/exhibitions", GetExhibitions)  // list all exhibitions
		current.GET("/exhibitions/:e_id", GetExhibition)  // show an exhibition
		current.GET("/search", SearchExhibitions)  // search exhibitions

		current.OPTIONS("/", EndpointsOptions) // http options
		current.OPTIONS("/exhibitions", EndpointsOptions) // http options
		current.OPTIONS("/exhibitions/:e_id", EndpointsOptions) // http options
		current.OPTIONS("/search", EndpointsOptions) // http options
	}

	// Home
	router.LoadHTMLGlob("tmpl/*")
	router.GET("/", func(c *gin.Context) {
		c.HTML(200, "index.tmpl", gin.H{
			"title": "JSON API for Dataset Arte Electrónico Argentino",
			"version": "v1",
		})
	})

	router.Run(fmt.Sprintf(":%s", httpPort))
}

func checkErr(err error) {
	if err != nil {
		log.Println(err)
	}
}

func initDb() *gorp.DbMap {
	db, err := sql.Open("sqlite3", dbFilename)
	checkErr(err)
	dbmap := &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}}
	dbmap.AddTableWithName(Exhibition{}, tableName).SetKeys(true, "Id")
	err = dbmap.CreateTablesIfNotExists()
	checkErr(err)
	return dbmap
}

func EndpointsOptions(c *gin.Context) {
	c.Writer.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET")
	c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type")
	c.Next()
}

func ApiCors() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Writer.Header().Add("Access-Control-Allow-Origin", "*")
		c.Next()
	}
}

func ListEndpoints(c *gin.Context) {
	api_url := fmt.Sprintf("%s%s/", apiHostname, apiPath)
	c.JSON(200, gin.H{
		"exhibitions_url": fmt.Sprintf("%s%s{/exhibition_id}{?page}", api_url, "exhibitions"),
		"search_url": fmt.Sprintf("%s%s?q={query}{&year,when,technique,curator,artist,work,page}", api_url, "search"),
	})
}

// http http://localhost:8080/api/v1/exhibitions
func GetExhibitions(c *gin.Context) {
	var exhibitions []Exhibition
	page := c.DefaultQuery("page", "1")
	query := PaginateQuery("SELECT * FROM exhibiciones ORDER BY id", page)
	_, err := dbmap.Select(&exhibitions, query)
	checkErr(err)

	if err == nil {
		content := newResourceObjects(exhibitions, "exhibitions", page)
		c.JSON(200, content)
	} else {
		jsonErr := &Error{"not_found", 404, "Not Found Error", "No exhibition found."}
		c.JSON(404, jsonErr)
	}
}

// http http://localhost:8080/api/v1/exhibitions/1
func GetExhibition(c *gin.Context) {
	id := c.Params.ByName("e_id")
	var exhibition Exhibition
	err := dbmap.SelectOne(&exhibition, "SELECT * FROM exhibiciones WHERE id=? LIMIT 1", id)
	checkErr(err)

	if err == nil {
		content := newResourceObject(exhibition)
		c.JSON(200, content)
	} else {
		jsonErr := &Error{"not_found", 404, "Not Found Error", "Your query didn't match any exhibition."}
		c.JSON(404, jsonErr)
	}
}

// http http://localhost:8080/api/v1/search?q=festival[&year=1998[&when=since[&technique=videoarte[&artist=rytel[&curator=taquini[&work=sheep]]]]]]
func SearchExhibitions(c *gin.Context) {
	var exhibitions []Exhibition
	q := c.Query("q")
	year := c.Query("year")
	when := c.Query("when")
	where := c.Query("where")
	curator := c.Query("curator")
	artist := c.Query("artist")
	technique := c.Query("technique")
	work := c.Query("work")
	page := c.DefaultQuery("page", "1")

	and_year := ""
	if year != "" {
		y, yerr := strconv.ParseInt(year, 10, 32)
		checkErr(yerr)

		cmp := "="
		switch when {
		case "until", "UNTIL":
			cmp = "<="
		case "since", "SINCE":
			cmp = ">="
		}

		and_year = fmt.Sprintf("AND anio %s %d", cmp, y)
	}

	and_where := ""
	if where != "" {
		and_where = fmt.Sprintf("AND nombre_locacion LIKE '%%%s%%'", where)
	}

	and_work := ""
	if work != "" {
		and_work = fmt.Sprintf("AND nombre_obra LIKE '%%%s%%'", work)
	}

	and_technique := ""
	if technique != "" {
		and_technique = fmt.Sprintf("AND tecnica LIKE '%%%s%%'", technique)
	}

	and_curator := ""
	if curator != "" {
		and_curator = fmt.Sprintf("AND curadores  MATCH '*%s*'", curator)
	}

	and_artist := ""
	if artist != "" {
		and_artist = fmt.Sprintf("AND artistas MATCH '*%s*'", artist)
	}

	query := fmt.Sprintf(`SELECT *
FROM exhibiciones
NATURAL JOIN artistas
NATURAL JOIN curadores
WHERE nombre_exhibicion LIKE '%%%s%%'
%s %s %s %s %s %s`, q,
		and_year, and_where, and_technique, and_work, and_artist, and_curator)

	query = PaginateQuery(query, page)
	_, err := dbmap.Select(&exhibitions, query)
	checkErr(err)

	if err == nil {
		content := newResourceObjects(exhibitions, "search", page)
		c.JSON(200, content)
	} else {
		jsonErr := &Error{"not_found", 404, "Not Found Error", "Your query didn't match any exhibition."}
		c.JSON(404, jsonErr)
	}
}
<commit_msg>template dir can be passed as an environmental var<commit_after>package main

import (
	"os"
	"fmt"
	"log"
	"path"
	"time"
	"strconv"
 	"database/sql"
	"github.com/coopernurse/gorp"
	_ "github.com/mattn/go-sqlite3"
	"github.com/aviddiviner/gin-limit"
	"github.com/gin-gonic/gin"
)

var (
	tmplDirectory string = "./tmpl"
	dbFilename    string = "./db/electronicArtArgentina.sqlite3"
	tableName     string = "exhibiciones"
	httpPort      string = "8080"
	apiHostname   string = "http://arte-electronico.cyberpunk.com.ar"
	apiPath       string = "/api"
	perPage       int = 20
	firstPage     string = "1"
	lastPage      string = "80" // (1593 / perPage) // TODO FIX ME

)

var dbmap = initDb()

type (
	Exhibition struct {
		Id         int64 `db:"id" json:"-"`
		Anio       int `db:"anio" json:"year"`
		Nombre     string `db:"nombre_exhibicion" json:"exhibition"`
		Locacion   string `db:"nombre_locacion" json:"location"`
		Curador0   string `db:"curador_0" json:"curator_0,omitempty"`
		Curador1   string `db:"curador_1" json:"curator_1,omitempty"`
		Curador2   string `db:"curador_2" json:"curator_2,omitempty"`
		Curador3   string `db:"curador_3" json:"curator_3,omitempty"`
		FechaIncio time.Time `db:"fecha_inicio" json:"start"`
		FechaFin   time.Time `db:"fecha_finalizacion" json:"end"`
		Obra       string `db:"nombre_obra" json:"work,omitempty"`
		Artista0   string `db:"nombre_artista_0" json:"artist_0,omitempty"`
		Artista1   string `db:"nombre_artista_1" json:"artist_1,omitempty"`
		Artista2   string `db:"nombre_artista_2" json:"artist_2,omitempty"`
		Artista3   string `db:"nombre_artista_3" json:"artist_3,omitempty"`
		Artista4   string `db:"nombre_artista_4" json:"artist_3,omitempty"`
		Artista5   string `db:"nombre_artista_5" json:"artist_5,omitempty"`
		Artista6   string `db:"nombre_artista_6" json:"artist_6,omitempty"`
		Artista7   string `db:"nombre_artista_7" json:"artist_7,omitempty"`
		Artista8   string `db:"nombre_artista_8" json:"artist_8,omitempty"`
		Tecnica    string `db:"tecnica" json:"technique,omitempty"`
	}

	PaginationLinks struct {
		First       string `json:"first"`
		Last       string `json:"last"`
		Prev       string `json:"prev"`
		Next       string `json:"next"`
	}

	ResourceObject struct {
		Id         int64 `json:"id"`
		Type       string `json:"type"`
		Attributes Exhibition `json:"attributes"`
	}

	ResourceObjects struct {
		Data []ResourceObject `json:"data"`
		Links PaginationLinks `json:"links"`
	}

	Error struct {
		Id     string `json:"id"`
		Status int `json:"status"`
		Title  string `json:"title"`
		Detail string `json:"detail"`
	}

)

func paginationLink(endpoint string, pg string, which string) string {
	page, err := strconv.ParseInt(pg, 10, 32)
	checkErr(err)
	first, ferr := strconv.ParseInt(firstPage, 10, 32)
	checkErr(ferr)
	last, lerr := strconv.ParseInt(lastPage, 10, 32)
	checkErr(lerr)

	switch which {
	case "prev":
		if page > first {
			page = page - 1
		}
	case "next":
		if page <= last {
			page = page + 1
		}
	}

	link := fmt.Sprintf("%s/%s?page=%d", apiPath, endpoint, page)
	if endpoint == "search" && which == "last" { // TODO ugly
		link = ""
	}

	return link
}

func newResourceObject(attr Exhibition) ResourceObject {
    return ResourceObject {
        Id: attr.Id,
        Type: "exhibitions",
        Attributes: attr,
    }
}

func newResourceObjects (exhibitions []Exhibition, endpoint string, pg string) ResourceObjects {
	resources := make([]ResourceObject, 0)
	for e := range exhibitions {
		resources = append(resources, newResourceObject(exhibitions[e]))
	}

	links := PaginationLinks {
		First: paginationLink(endpoint, firstPage, "first"),
		Last: paginationLink(endpoint, lastPage, "last"),
		Prev: paginationLink(endpoint, pg, "prev"),
		Next: paginationLink(endpoint, pg, "next"),
	}

	return ResourceObjects{Data: resources, Links: links}
}

func pageOffset(pg string) int {
	page, err := strconv.Atoi(pg)
	if err != nil {
		page = 1
	}

	return perPage * (page - 1)
}

func PaginateQuery(query, pg string) string {
	return fmt.Sprintf("%s LIMIT %d OFFSET %d", query, perPage, pageOffset(pg))
}

func init() {
	p := os.Getenv("HTTP_PORT")
	if p != "" {
		httpPort = p
	}

	d := os.Getenv("DB_FILENAME")
	if d != "" {
		dbFilename = d
	}

	t:= os.Getenv("TMPL_DIR")
	if t != "" {
		tmplDirectory = t
	}
}

func main() {
	router := gin.Default()
	router.Use(ApiCors())
	router.Use(limit.MaxAllowed(5))

	// API v1
	v1 := router.Group("api/v1")
	{
		v1.GET("/", ListEndpoints)  // list all exhibitions
		v1.GET("/exhibitions", GetExhibitions)  // list all exhibitions
		v1.GET("/exhibitions/:e_id", GetExhibition)  // show an exhibition
		v1.GET("/search", SearchExhibitions)  // search exhibitions

		v1.OPTIONS("/", EndpointsOptions) // http options
		v1.OPTIONS("/exhibitions", EndpointsOptions) // http options
		v1.OPTIONS("/exhibitions/:e_id", EndpointsOptions) // http options
		v1.OPTIONS("/search", EndpointsOptions) // http options
	}

	// API -- TODO improve this
	current := router.Group("api")
	{
		current.GET("/", ListEndpoints)  // list all exhibitions
		current.GET("/exhibitions", GetExhibitions)  // list all exhibitions
		current.GET("/exhibitions/:e_id", GetExhibition)  // show an exhibition
		current.GET("/search", SearchExhibitions)  // search exhibitions

		current.OPTIONS("/", EndpointsOptions) // http options
		current.OPTIONS("/exhibitions", EndpointsOptions) // http options
		current.OPTIONS("/exhibitions/:e_id", EndpointsOptions) // http options
		current.OPTIONS("/search", EndpointsOptions) // http options
	}

	// Home
	router.LoadHTMLGlob(path.Join(tmplDirectory, "*"))
	router.GET("/", func(c *gin.Context) {
		c.HTML(200, "index.tmpl", gin.H{
			"title": "JSON API for Dataset Arte Electrónico Argentino",
			"version": "v1",
		})
	})

	router.Run(fmt.Sprintf(":%s", httpPort))
}

func checkErr(err error) {
	if err != nil {
		log.Println(err)
	}
}

func initDb() *gorp.DbMap {
	db, err := sql.Open("sqlite3", dbFilename)
	checkErr(err)
	dbmap := &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}}
	dbmap.AddTableWithName(Exhibition{}, tableName).SetKeys(true, "Id")
	err = dbmap.CreateTablesIfNotExists()
	checkErr(err)
	return dbmap
}

func EndpointsOptions(c *gin.Context) {
	c.Writer.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET")
	c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type")
	c.Next()
}

func ApiCors() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Writer.Header().Add("Access-Control-Allow-Origin", "*")
		c.Next()
	}
}

func ListEndpoints(c *gin.Context) {
	api_url := fmt.Sprintf("%s%s/", apiHostname, apiPath)
	c.JSON(200, gin.H{
		"exhibitions_url": fmt.Sprintf("%s%s{/exhibition_id}{?page}", api_url, "exhibitions"),
		"search_url": fmt.Sprintf("%s%s?q={query}{&year,when,technique,curator,artist,work,page}", api_url, "search"),
	})
}

// http http://localhost:8080/api/v1/exhibitions
func GetExhibitions(c *gin.Context) {
	var exhibitions []Exhibition
	page := c.DefaultQuery("page", "1")
	query := PaginateQuery("SELECT * FROM exhibiciones ORDER BY id", page)
	_, err := dbmap.Select(&exhibitions, query)
	checkErr(err)

	if err == nil {
		content := newResourceObjects(exhibitions, "exhibitions", page)
		c.JSON(200, content)
	} else {
		jsonErr := &Error{"not_found", 404, "Not Found Error", "No exhibition found."}
		c.JSON(404, jsonErr)
	}
}

// http http://localhost:8080/api/v1/exhibitions/1
func GetExhibition(c *gin.Context) {
	id := c.Params.ByName("e_id")
	var exhibition Exhibition
	err := dbmap.SelectOne(&exhibition, "SELECT * FROM exhibiciones WHERE id=? LIMIT 1", id)
	checkErr(err)

	if err == nil {
		content := newResourceObject(exhibition)
		c.JSON(200, content)
	} else {
		jsonErr := &Error{"not_found", 404, "Not Found Error", "Your query didn't match any exhibition."}
		c.JSON(404, jsonErr)
	}
}

// http http://localhost:8080/api/v1/search?q=festival[&year=1998[&when=since[&technique=videoarte[&artist=rytel[&curator=taquini[&work=sheep]]]]]]
func SearchExhibitions(c *gin.Context) {
	var exhibitions []Exhibition
	q := c.Query("q")
	year := c.Query("year")
	when := c.Query("when")
	where := c.Query("where")
	curator := c.Query("curator")
	artist := c.Query("artist")
	technique := c.Query("technique")
	work := c.Query("work")
	page := c.DefaultQuery("page", "1")

	and_year := ""
	if year != "" {
		y, yerr := strconv.ParseInt(year, 10, 32)
		checkErr(yerr)

		cmp := "="
		switch when {
		case "until", "UNTIL":
			cmp = "<="
		case "since", "SINCE":
			cmp = ">="
		}

		and_year = fmt.Sprintf("AND anio %s %d", cmp, y)
	}

	and_where := ""
	if where != "" {
		and_where = fmt.Sprintf("AND nombre_locacion LIKE '%%%s%%'", where)
	}

	and_work := ""
	if work != "" {
		and_work = fmt.Sprintf("AND nombre_obra LIKE '%%%s%%'", work)
	}

	and_technique := ""
	if technique != "" {
		and_technique = fmt.Sprintf("AND tecnica LIKE '%%%s%%'", technique)
	}

	and_curator := ""
	if curator != "" {
		and_curator = fmt.Sprintf("AND curadores  MATCH '*%s*'", curator)
	}

	and_artist := ""
	if artist != "" {
		and_artist = fmt.Sprintf("AND artistas MATCH '*%s*'", artist)
	}

	query := fmt.Sprintf(`SELECT *
FROM exhibiciones
NATURAL JOIN artistas
NATURAL JOIN curadores
WHERE nombre_exhibicion LIKE '%%%s%%'
%s %s %s %s %s %s`, q,
		and_year, and_where, and_technique, and_work, and_artist, and_curator)

	query = PaginateQuery(query, page)
	_, err := dbmap.Select(&exhibitions, query)
	checkErr(err)

	if err == nil {
		content := newResourceObjects(exhibitions, "search", page)
		c.JSON(200, content)
	} else {
		jsonErr := &Error{"not_found", 404, "Not Found Error", "Your query didn't match any exhibition."}
		c.JSON(404, jsonErr)
	}
}
<|endoftext|>


<commit_before>package routeros

import (
	"crypto/md5"
	"encoding/hex"
	"errors"
	"fmt"
	"github.com/jcelliott/lumber"
	"io"
	"net"
	"strings"
)

// A reply can contain multiple pairs. A pair is a string key->value.
// A reply can also contain subpairs, that is, a array of pair arrays.
type Reply struct {
	Pairs    []Pair
	SubPairs [][]Pair
}

func (r *Reply) GetPairVal(key string) (string, error) {
	for _, p := range r.Pairs {
		if p.Key == key {
			return p.Value, nil
		}
	}
	return "", errors.New("key not found")
}

func GetPairVal(pairs []Pair, key string) (string, error) {
	for _, p := range pairs {
		if p.Key == key {
			return p.Value, nil
		}
	}
	return "", errors.New("key not found")
}

// Client is a RouterOS API client.
type Client struct {
	// Network Address.
	// E.g. "10.0.0.1:8728" or "router.example.com:8728"
	address  string
	user     string
	password string
	logger   *lumber.Logger
	debug    bool     // debug logging enabled
	ready    bool     // Ready for work (login ok and connection not terminated)
	conn     net.Conn // Connection to pass around
}

// Pair is a Key-Value pair for RouterOS Attribute, Query, and Reply words
// use slices of pairs instead of map because we care about order
type Pair struct {
	Key   string
	Value string
	// Op is used for Query words to signify logical operations
	// valid operators are -, =, <, >
	// see http://wiki.mikrotik.com/wiki/Manual:API#Queries for details.
	Op string
}

type Query struct {
	Pairs    []Pair
	Op       string
	Proplist []string
}

func NewPair(key string, value string) *Pair {
	p := new(Pair)
	p.Key = key
	p.Value = value
	return p
}

// Create a new instance of the RouterOS API client
func New(address string) (*Client, error) {
	// basic validation of host address
	_, _, err := net.SplitHostPort(address)
	if err != nil {
		return nil, err
	}

	var c Client
	c.address = address

	return &c, nil
}

func (c *Client) Close() {
	c.conn.Close()
}

func (c *Client) Connect(user string, password string) error {
	conn, err := net.Dial("tcp", c.address)
	if err != nil {
		return err
	}

	// stash conn in instance
	c.conn = conn

	// try to log in
	res, err := c.Call("/login", nil)
	if err != nil {
		return err
	}

	// handle challenge/response
	challengeEnc, err := res.GetPairVal("ret")
	if err != nil {
		return errors.New("Didn't get challenge from ROS")
	}
	challenge, err := hex.DecodeString(challengeEnc)
	if err != nil {
		return err
	}
	h := md5.New()
	io.WriteString(h, "\000")
	io.WriteString(h, password)
	h.Write(challenge)
	resp := fmt.Sprintf("00%x", h.Sum(nil))
	var loginParams []Pair
	loginParams = append(loginParams, *NewPair("name", password))
	loginParams = append(loginParams, *NewPair("response", resp))

	// try to log in again with challenge/response
	res, err = c.Call("/login", loginParams)
	if err != nil {
		return err
	}

	if len(res.Pairs) > 0 {
		return fmt.Errorf("Unexpected result on login: %+v", res)
	}

	return nil
}

func (c *Client) Query(command string, q Query) (Reply, error) {
	err := c.send(command)
	if err != nil {
		return Reply{}, err
	}

	// Set property list if present
	if len(q.Proplist) > 0 {
		proplist := fmt.Sprintf("=.proplist=%s", strings.Join(q.Proplist, ","))
		err = c.send(proplist)
		if err != nil {
			return Reply{}, err
		}
	}

	// send params if we got them
	if len(q.Pairs) > 0 {
		for _, v := range q.Pairs {
			word := fmt.Sprintf("?%s%s=%s", v.Op, v.Key, v.Value)
			c.send(word)
		}

		if q.Op != "" {
			word := fmt.Sprintf("?#%s", q.Op)
			c.send(word)
		}
	}

	// send terminator
	err = c.send("")
	if err != nil {
		return Reply{}, err
	}

	res, err := c.receive()
	if err != nil {
		return Reply{}, err
	}

	return res, nil
}

func (c *Client) Call(command string, params []Pair) (Reply, error) {
	err := c.send(command)
	if err != nil {
		return Reply{}, err
	}

	// send params if we got them
	if len(params) > 0 {
		for _, v := range params {
			word := fmt.Sprintf("=%s=%s", v.Key, v.Value)
			c.send(word)
		}
	}

	// send terminator
	err = c.send("")
	if err != nil {
		return Reply{}, err
	}

	res, err := c.receive()
	if err != nil {
		return Reply{}, err
	}

	return res, nil
}
<commit_msg>fix sending password as username<commit_after>package routeros

import (
	"crypto/md5"
	"encoding/hex"
	"errors"
	"fmt"
	"github.com/jcelliott/lumber"
	"io"
	"net"
	"strings"
)

// A reply can contain multiple pairs. A pair is a string key->value.
// A reply can also contain subpairs, that is, a array of pair arrays.
type Reply struct {
	Pairs    []Pair
	SubPairs [][]Pair
}

func (r *Reply) GetPairVal(key string) (string, error) {
	for _, p := range r.Pairs {
		if p.Key == key {
			return p.Value, nil
		}
	}
	return "", errors.New("key not found")
}

func GetPairVal(pairs []Pair, key string) (string, error) {
	for _, p := range pairs {
		if p.Key == key {
			return p.Value, nil
		}
	}
	return "", errors.New("key not found")
}

// Client is a RouterOS API client.
type Client struct {
	// Network Address.
	// E.g. "10.0.0.1:8728" or "router.example.com:8728"
	address  string
	user     string
	password string
	logger   *lumber.Logger
	debug    bool     // debug logging enabled
	ready    bool     // Ready for work (login ok and connection not terminated)
	conn     net.Conn // Connection to pass around
}

// Pair is a Key-Value pair for RouterOS Attribute, Query, and Reply words
// use slices of pairs instead of map because we care about order
type Pair struct {
	Key   string
	Value string
	// Op is used for Query words to signify logical operations
	// valid operators are -, =, <, >
	// see http://wiki.mikrotik.com/wiki/Manual:API#Queries for details.
	Op string
}

type Query struct {
	Pairs    []Pair
	Op       string
	Proplist []string
}

func NewPair(key string, value string) *Pair {
	p := new(Pair)
	p.Key = key
	p.Value = value
	return p
}

// Create a new instance of the RouterOS API client
func New(address string) (*Client, error) {
	// basic validation of host address
	_, _, err := net.SplitHostPort(address)
	if err != nil {
		return nil, err
	}

	var c Client
	c.address = address

	return &c, nil
}

func (c *Client) Close() {
	c.conn.Close()
}

func (c *Client) Connect(user string, password string) error {
	conn, err := net.Dial("tcp", c.address)
	if err != nil {
		return err
	}

	// stash conn in instance
	c.conn = conn

	// try to log in
	res, err := c.Call("/login", nil)
	if err != nil {
		return err
	}

	// handle challenge/response
	challengeEnc, err := res.GetPairVal("ret")
	if err != nil {
		return errors.New("Didn't get challenge from ROS")
	}
	challenge, err := hex.DecodeString(challengeEnc)
	if err != nil {
		return err
	}
	h := md5.New()
	io.WriteString(h, "\000")
	io.WriteString(h, password)
	h.Write(challenge)
	resp := fmt.Sprintf("00%x", h.Sum(nil))
	var loginParams []Pair
	loginParams = append(loginParams, *NewPair("name", user))
	loginParams = append(loginParams, *NewPair("response", resp))

	// try to log in again with challenge/response
	res, err = c.Call("/login", loginParams)
	if err != nil {
		return err
	}

	if len(res.Pairs) > 0 {
		return fmt.Errorf("Unexpected result on login: %+v", res)
	}

	return nil
}

func (c *Client) Query(command string, q Query) (Reply, error) {
	err := c.send(command)
	if err != nil {
		return Reply{}, err
	}

	// Set property list if present
	if len(q.Proplist) > 0 {
		proplist := fmt.Sprintf("=.proplist=%s", strings.Join(q.Proplist, ","))
		err = c.send(proplist)
		if err != nil {
			return Reply{}, err
		}
	}

	// send params if we got them
	if len(q.Pairs) > 0 {
		for _, v := range q.Pairs {
			word := fmt.Sprintf("?%s%s=%s", v.Op, v.Key, v.Value)
			c.send(word)
		}

		if q.Op != "" {
			word := fmt.Sprintf("?#%s", q.Op)
			c.send(word)
		}
	}

	// send terminator
	err = c.send("")
	if err != nil {
		return Reply{}, err
	}

	res, err := c.receive()
	if err != nil {
		return Reply{}, err
	}

	return res, nil
}

func (c *Client) Call(command string, params []Pair) (Reply, error) {
	err := c.send(command)
	if err != nil {
		return Reply{}, err
	}

	// send params if we got them
	if len(params) > 0 {
		for _, v := range params {
			word := fmt.Sprintf("=%s=%s", v.Key, v.Value)
			c.send(word)
		}
	}

	// send terminator
	err = c.send("")
	if err != nil {
		return Reply{}, err
	}

	res, err := c.receive()
	if err != nil {
		return Reply{}, err
	}

	return res, nil
}
<|endoftext|>


<commit_before>package chaining

import "jecolasurdo/go-chaining/injectionbehavior"

// Flush returns the context's error and final result, and resets the context back to its default state.
func (c *Context) Flush() (interface{}, error) {
	localError := c.LocalError
	finalResult := c.PreviousActionResult
	c.LocalError = nil
	c.PreviousActionResult = nil
	return finalResult, localError
}

// ApplyNullary executes an action which takes no arguments and returns only an error.
//
// Since the supplied action returns no value aside from an error, the context will supply nil as a pseudo-result
// for the supplied action. The context will then apply that nil to the next action called within the context,
// unless the override behavior for the next action dictates otherwise.
//
// The error returned by the supplied action is also applied to the current context.
// If error is not nil, subsequent actions executed within the same context will be ignored.
func (c *Context) ApplyNullary(action func() error) {
	TODO: Restate in terms of ApplyUnaryIface
	if c.LocalError == nil {
		c.LocalError = action()
	}
}

// ApplyNullaryIface executes an action which takes no arguments and returns a tuple of (interface{}, error).
//
// The context will apply the supplied action's interface{} result to the next action called within the context,
// unless the override behavior for the next action dictates otherwise.
//
// The error returned by the supplied action is also applied to the current context.
// If error is not nil, subsequent actions executed within the same context will be ignored.
func (c *Context) ApplyNullaryIface(action func() (interface{}, error), behavior injectionbehavior.InjectionBehavior) {
	restatedAction := func(val interface{}) (interface{}, error) {
		return action()
	}
	c.ApplyUnaryIface(restatedAction, ActionArg{Behavior: behavior})
}

// ApplyUnary executes an action which takes one argument returns only an error.
//
// The single interface{} argument accepted by the action can be supplied via the arg parameter.
//
// Since the supplied action returns no value aside from an error, the context will supply nil as a pseudo-result
// for the supplied action. The context will then apply that nil to the next action called within the context,
// unless the override behavior for the next action dictates otherwise.
//
// The error returned by the supplied action is also applied to the current context.
// If error is not nil, subsequent actions executed within the same context will be ignored.
func (c *Context) ApplyUnary(action func(interface{}) error, arg ActionArg) {
	restatedAction := func(val interface{}) (interface{}, error) {
		err := action(val)
		return nil, err
	}
	c.ApplyUnaryIface(restatedAction, arg)
}

// ApplyUnaryIface executes an action which takes one argument, and returns a tuple of (interface{}, error).
//
// The single interface{} argument accepted by the action can be supplied via the arg parameter.
//
// The context will apply the supplied action's interface{} result to the next action called within the context,
// unless the override behavior for the next action dictates otherwise.
//
// The error returned by the supplied action is also applied to the current context.
// If error is not nil, subsequent actions executed within the same context will be ignored.
func (c *Context) ApplyUnaryIface(action func(interface{}) (interface{}, error), arg ActionArg) {
	if c.LocalError != nil {
		return
	}
	var valueToInject interface{}
	if arg.Behavior == injectionbehavior.InjectSuppliedValue {
		valueToInject = arg.Value
	} else {
		valueToInject = c.PreviousActionResult
	}
	result, err := action(valueToInject)
	c.LocalError = err
	c.PreviousActionResult = result
}

// ApplyNullaryBool executes an action which takes no arguments and returns a tuple of (bool, error).
//
// The context will apply the supplied action's bool result to the next action called within the context,
// unless the override behavior for the next action dictates otherwise.
//
// The error returned by the supplied action is also applied to the current context.
// If error is not nil, subsequent actions executed within the same context will be ignored.
//
// In addition to threading the (bool, error) tuple into the current context, NullaryBool itself also returns a bool.
// This is useful for inlining the method in boolean statements.
func (c *Context) ApplyNullaryBool(action func() (bool, error)) bool {
	TODO: Restate in terms of ApplyUnaryIface
	if c.LocalError != nil {
		return false
	}

	result, err := action()
	c.LocalError = err
	return result
}

// ApplyUnaryBool executes an action which takes one argument and returns a tuple of (bool, error).
//
// The single interface{} argument accepted by the action can be supplied via the arg parameter.
//
// The context will apply the supplied action's bool result to the next action called within the context,
// unless the override behavior for the next action dictates otherwise.
//
// The error returned by the supplied action is also applied to the current context.
// If error is not nil, subsequent actions executed within the same context will be ignored.
//
// In addition to threading the (bool, error) tuple into the current context, UnaryBool itself also returns a bool.
// This is useful for inlining the method in boolean statements.
func (c *Context) ApplyUnaryBool(action func(interface{}) (bool, error), arg ActionArg) bool {
	restatedAction := func(val interface{}) (interface{}, error) {
		return action(val)
	}
	c.ApplyUnaryIface(restatedAction, arg)
	return c.PreviousActionResult.(bool)
}
<commit_msg>Restated final two methods in terms of ApplyUnaryIface<commit_after>package chaining

import "jecolasurdo/go-chaining/injectionbehavior"

// Flush returns the context's error and final result, and resets the context back to its default state.
func (c *Context) Flush() (interface{}, error) {
	localError := c.LocalError
	finalResult := c.PreviousActionResult
	c.LocalError = nil
	c.PreviousActionResult = nil
	return finalResult, localError
}

// ApplyNullary executes an action which takes no arguments and returns only an error.
//
// Since the supplied action returns no value aside from an error, the context will supply nil as a pseudo-result
// for the supplied action. The context will then apply that nil to the next action called within the context,
// unless the override behavior for the next action dictates otherwise.
//
// The error returned by the supplied action is also applied to the current context.
// If error is not nil, subsequent actions executed within the same context will be ignored.
func (c *Context) ApplyNullary(action func() error, behavior injectionbehavior.InjectionBehavior) {
	restatedAction := func(val interface{}) (interface{}, error) {
		return nil, action()
	}
	c.ApplyUnaryIface(restatedAction, ActionArg{Behavior: behavior})
}

// ApplyNullaryIface executes an action which takes no arguments and returns a tuple of (interface{}, error).
//
// The context will apply the supplied action's interface{} result to the next action called within the context,
// unless the override behavior for the next action dictates otherwise.
//
// The error returned by the supplied action is also applied to the current context.
// If error is not nil, subsequent actions executed within the same context will be ignored.
func (c *Context) ApplyNullaryIface(action func() (interface{}, error), behavior injectionbehavior.InjectionBehavior) {
	restatedAction := func(val interface{}) (interface{}, error) {
		return action()
	}
	c.ApplyUnaryIface(restatedAction, ActionArg{Behavior: behavior})
}

// ApplyUnary executes an action which takes one argument returns only an error.
//
// The single interface{} argument accepted by the action can be supplied via the arg parameter.
//
// Since the supplied action returns no value aside from an error, the context will supply nil as a pseudo-result
// for the supplied action. The context will then apply that nil to the next action called within the context,
// unless the override behavior for the next action dictates otherwise.
//
// The error returned by the supplied action is also applied to the current context.
// If error is not nil, subsequent actions executed within the same context will be ignored.
func (c *Context) ApplyUnary(action func(interface{}) error, arg ActionArg) {
	restatedAction := func(val interface{}) (interface{}, error) {
		return nil, action(val)
	}
	c.ApplyUnaryIface(restatedAction, arg)
}

// ApplyUnaryIface executes an action which takes one argument, and returns a tuple of (interface{}, error).
//
// The single interface{} argument accepted by the action can be supplied via the arg parameter.
//
// The context will apply the supplied action's interface{} result to the next action called within the context,
// unless the override behavior for the next action dictates otherwise.
//
// The error returned by the supplied action is also applied to the current context.
// If error is not nil, subsequent actions executed within the same context will be ignored.
func (c *Context) ApplyUnaryIface(action func(interface{}) (interface{}, error), arg ActionArg) {
	if c.LocalError != nil {
		return
	}
	var valueToInject interface{}
	if arg.Behavior == injectionbehavior.InjectSuppliedValue {
		valueToInject = arg.Value
	} else {
		valueToInject = c.PreviousActionResult
	}
	result, err := action(valueToInject)
	c.LocalError = err
	c.PreviousActionResult = result
}

// ApplyNullaryBool executes an action which takes no arguments and returns a tuple of (bool, error).
//
// The context will apply the supplied action's bool result to the next action called within the context,
// unless the override behavior for the next action dictates otherwise.
//
// The error returned by the supplied action is also applied to the current context.
// If error is not nil, subsequent actions executed within the same context will be ignored.
//
// In addition to threading the (bool, error) tuple into the current context, NullaryBool itself also returns a bool.
// This is useful for inlining the method in boolean statements.
func (c *Context) ApplyNullaryBool(action func() (bool, error), arg ActionArg) bool {
	restatedAction := func(val interface{}) (interface{}, error) {
		return action()
	}
	c.ApplyUnaryIface(restatedAction, arg)
}

// ApplyUnaryBool executes an action which takes one argument and returns a tuple of (bool, error).
//
// The single interface{} argument accepted by the action can be supplied via the arg parameter.
//
// The context will apply the supplied action's bool result to the next action called within the context,
// unless the override behavior for the next action dictates otherwise.
//
// The error returned by the supplied action is also applied to the current context.
// If error is not nil, subsequent actions executed within the same context will be ignored.
//
// In addition to threading the (bool, error) tuple into the current context, UnaryBool itself also returns a bool.
// This is useful for inlining the method in boolean statements.
func (c *Context) ApplyUnaryBool(action func(interface{}) (bool, error), arg ActionArg) bool {
	restatedAction := func(val interface{}) (interface{}, error) {
		return action(val)
	}
	c.ApplyUnaryIface(restatedAction, arg)
	return c.PreviousActionResult.(bool)
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"github.com/jinzhu/gorm"
	_ "github.com/lib/pq"
	"github.com/yvasiyarov/go-metrics"
	"github.com/yvasiyarov/gorelic"
	"log"
	"net/http"
	"os"
	"time"
)

func main() {
	i := Impl{}
	i.InitDB()
	i.InitSchema()

	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	api.Use(&NewRelicMiddleware{
		License: os.Getenv("NEWRELIC_LICENCE_KEY"),
		Name:    "Puffin Event API",
		Verbose: true,
	})
	//api.Use(&rest.AccessLogJsonMiddleware{})
	router, err := rest.MakeRouter(
		rest.Post("/events", i.PostEvent),
		rest.Post("/installations", i.PostInstallation),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)
	log.Fatal(http.ListenAndServe("0.0.0.0:"+os.Getenv("PORT"), api.MakeHandler()))
}

type NewRelicMiddleware struct {
	License string
	Name    string
	Verbose bool
	agent   *gorelic.Agent
}

func (mw *NewRelicMiddleware) MiddlewareFunc(handler rest.HandlerFunc) rest.HandlerFunc {

	mw.agent = gorelic.NewAgent()
	mw.agent.NewrelicLicense = mw.License
	mw.agent.HTTPTimer = metrics.NewTimer()
	mw.agent.Verbose = mw.Verbose
	mw.agent.NewrelicName = mw.Name
	mw.agent.CollectHTTPStat = true
	mw.agent.Run()

	return func(writer rest.ResponseWriter, request *rest.Request) {

		handler(writer, request)

		// the timer middleware keeps track of the time
		startTime := request.Env["START_TIME"].(*time.Time)
		mw.agent.HTTPTimer.UpdateSince(*startTime)
	}
}

type Event struct {
	Id                          int64      `json:"id"`
	DeviceId                    string     `sql:"size:40" json:"deviceId"`
	MeasuredAt                  time.Time  `json:"measuredAt"`
	Measurement                 string     `sql:"size:1024" json:"measurement"`
	Value                       float64    `json:"value"`
	Fields                      string     `sql:"size:1024" json:"fields"`
	Tags                        string     `sql:"size:1024" json:"tags"`
	Note                        string     `sql:"size:1024" json:"note"`
	Tenant                      string     `sql:"size:30" json:"-"`
	CreatedAt                   time.Time  `json:"createdAt"`
	UpdatedAt                   time.Time  `json:"updatedAt"`
	DeletedAt                   time.Time  `json:"-"`
	NotificationIntervalMinutes int64      `json:"notificationIntervalMinutes"`
	NotificationShownAt         *time.Time `json:"notificationShownAt"`
	NotificationDismissedAt     *time.Time `json:"notificationDismissedAt"`
	NotificationAcceptedAt      *time.Time `json:"notificationAcceptedAt"`
	StartAt                     *time.Time `json:"startAt"`
	EndAt                       *time.Time `json:"EndAt"`
}

type Installation struct {
	Id                  int64     `json:"id"`
	DeviceType          string    `sql:"size:40" json:"deviceType"`
	DeviceManufacturer  string    `sql:"size:40" json:"deviceManufacturer"`
	DeviceModel         string    `sql:"size:40" json:"deviceModel"`
	DeviceProduct       string    `sql:"size:40" json:"deviceProduct"`
	DeviceSdk           string    `sql:"size:40" json:"deviceSdk"`
	DeviceSystemLocale  string    `sql:"size:40" json:"deviceSystemLocale"`
	DeviceAdvertisingId string    `sql:"size:40" json:"deviceAdverstisingId"`
	DeviceMobileCarrier string    `sql:"size:40" json:"deviceMobileCarrier"`
	AppVersion          string    `sql:"size:40" json:"appVersion"`
	AppVersionCode      string    `sql:"size:40" json:"appVersionCode"`
	CreatedAt           time.Time `json:"createdAt"`
	UpdatedAt           time.Time `json:"updatedAt"`
	DeletedAt           time.Time `json:"-"`
}

type Impl struct {
	DB gorm.DB
}

func (i *Impl) InitDB() {
	var err error
	connection := os.Getenv("DATABASE_URL")
	i.DB, err = gorm.Open("postgres", connection)
	if err != nil {
		log.Fatalf("Got error when connect database, the error is '%v'", err)
	}
	i.DB.LogMode(true)
}

func (i *Impl) InitSchema() {
	i.DB.AutoMigrate(&Event{})
	i.DB.AutoMigrate(&Installation{})
}

func (i *Impl) PostEvent(w rest.ResponseWriter, r *rest.Request) {
	event := Event{}
	if err := r.DecodeJsonPayload(&event); err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if err := i.DB.Create(&event).Error; err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusCreated)
	w.WriteJson(&event)
}

func (i *Impl) PostInstallation(w rest.ResponseWriter, r *rest.Request) {
	installation := Installation{}
	if err := r.DecodeJsonPayload(&installation); err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if err := i.DB.Save(&installation).Error; err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusCreated)
	w.WriteJson(&installation)
}
<commit_msg>include tenant in json<commit_after>package main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"github.com/jinzhu/gorm"
	_ "github.com/lib/pq"
	"github.com/yvasiyarov/go-metrics"
	"github.com/yvasiyarov/gorelic"
	"log"
	"net/http"
	"os"
	"time"
)

func main() {
	i := Impl{}
	i.InitDB()
	i.InitSchema()

	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	api.Use(&NewRelicMiddleware{
		License: os.Getenv("NEWRELIC_LICENCE_KEY"),
		Name:    "Puffin Event API",
		Verbose: true,
	})
	//api.Use(&rest.AccessLogJsonMiddleware{})
	router, err := rest.MakeRouter(
		rest.Post("/events", i.PostEvent),
		rest.Post("/installations", i.PostInstallation),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)
	log.Fatal(http.ListenAndServe("0.0.0.0:"+os.Getenv("PORT"), api.MakeHandler()))
}

type NewRelicMiddleware struct {
	License string
	Name    string
	Verbose bool
	agent   *gorelic.Agent
}

func (mw *NewRelicMiddleware) MiddlewareFunc(handler rest.HandlerFunc) rest.HandlerFunc {

	mw.agent = gorelic.NewAgent()
	mw.agent.NewrelicLicense = mw.License
	mw.agent.HTTPTimer = metrics.NewTimer()
	mw.agent.Verbose = mw.Verbose
	mw.agent.NewrelicName = mw.Name
	mw.agent.CollectHTTPStat = true
	mw.agent.Run()

	return func(writer rest.ResponseWriter, request *rest.Request) {

		handler(writer, request)

		// the timer middleware keeps track of the time
		startTime := request.Env["START_TIME"].(*time.Time)
		mw.agent.HTTPTimer.UpdateSince(*startTime)
	}
}

type Event struct {
	Id                          int64      `json:"id"`
	DeviceId                    string     `sql:"size:40" json:"deviceId"`
	MeasuredAt                  time.Time  `json:"measuredAt"`
	Measurement                 string     `sql:"size:1024" json:"measurement"`
	Value                       float64    `json:"value"`
	Fields                      string     `sql:"size:1024" json:"fields"`
	Tags                        string     `sql:"size:1024" json:"tags"`
	Note                        string     `sql:"size:1024" json:"note"`
	Tenant                      string     `sql:"size:30" json:"tenant"`
	CreatedAt                   time.Time  `json:"createdAt"`
	UpdatedAt                   time.Time  `json:"updatedAt"`
	DeletedAt                   time.Time  `json:"-"`
	NotificationIntervalMinutes int64      `json:"notificationIntervalMinutes"`
	NotificationShownAt         *time.Time `json:"notificationShownAt"`
	NotificationDismissedAt     *time.Time `json:"notificationDismissedAt"`
	NotificationAcceptedAt      *time.Time `json:"notificationAcceptedAt"`
	StartAt                     *time.Time `json:"startAt"`
	EndAt                       *time.Time `json:"EndAt"`
}

type Installation struct {
	Id                  int64     `json:"id"`
	DeviceType          string    `sql:"size:40" json:"deviceType"`
	DeviceManufacturer  string    `sql:"size:40" json:"deviceManufacturer"`
	DeviceModel         string    `sql:"size:40" json:"deviceModel"`
	DeviceProduct       string    `sql:"size:40" json:"deviceProduct"`
	DeviceSdk           string    `sql:"size:40" json:"deviceSdk"`
	DeviceSystemLocale  string    `sql:"size:40" json:"deviceSystemLocale"`
	DeviceAdvertisingId string    `sql:"size:40" json:"deviceAdverstisingId"`
	DeviceMobileCarrier string    `sql:"size:40" json:"deviceMobileCarrier"`
	AppVersion          string    `sql:"size:40" json:"appVersion"`
	AppVersionCode      string    `sql:"size:40" json:"appVersionCode"`
	CreatedAt           time.Time `json:"createdAt"`
	UpdatedAt           time.Time `json:"updatedAt"`
	DeletedAt           time.Time `json:"-"`
}

type Impl struct {
	DB gorm.DB
}

func (i *Impl) InitDB() {
	var err error
	connection := os.Getenv("DATABASE_URL")
	i.DB, err = gorm.Open("postgres", connection)
	if err != nil {
		log.Fatalf("Got error when connect database, the error is '%v'", err)
	}
	i.DB.LogMode(true)
}

func (i *Impl) InitSchema() {
	i.DB.AutoMigrate(&Event{})
	i.DB.AutoMigrate(&Installation{})
}

func (i *Impl) PostEvent(w rest.ResponseWriter, r *rest.Request) {
	event := Event{}
	if err := r.DecodeJsonPayload(&event); err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if err := i.DB.Create(&event).Error; err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusCreated)
	w.WriteJson(&event)
}

func (i *Impl) PostInstallation(w rest.ResponseWriter, r *rest.Request) {
	installation := Installation{}
	if err := r.DecodeJsonPayload(&installation); err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if err := i.DB.Save(&installation).Error; err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusCreated)
	w.WriteJson(&installation)
}
<|endoftext|>


<commit_before>package multisafego

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/httputil"
	"net/url"
	"reflect"
)

var (
	// TESTURL is the url to use in test mode
	TESTURL *url.URL
	// PRODURL is the url to use in production mode
	PRODURL *url.URL
)

func init() {
	TESTURL, _ = url.Parse("https://testapi.multisafepay.com")
	PRODURL, _ = url.Parse("https://api.multisafepay.com")
}

// MultiSafePay is the wrapper for calls to the Api.
// Use the New function to construct a basic variant
type MultiSafePay struct {
	apiKey  string
	baseURL *url.URL
	debug   bool
	Logs    chan errorLog
}

// A APIError occurs when the response has a non 200 status code
// default error values like a timeout will be wrapped in an APIError with code -1
type APIError struct {
	Code    int         `json:"error_code"`
	Message string      `json:"error_info"`
	Data    interface{} `json:"data"`
}

func (a APIError) String() string {
	return fmt.Sprintf("[%d] %s", a.Code, a.Message)
}

type errorLog struct {
	URL      string
	Request  []byte
	Response []byte
}

// New returns a basic multisafepay object that can be set in debug mode to dump requests/responses
func New(apiKey string, baseURL *url.URL, debug bool) *MultiSafePay {
	return &MultiSafePay{apiKey, baseURL, debug, make(chan errorLog, 100)}
}

func errorToAPIError(err error) *APIError {
	return &APIError{-1, err.Error(), nil}
}

// Path is a shortcut for prepending /v1/json to the path
func Path(path string) string {
	return "/v1/json" + path
}

func (m *MultiSafePay) log(url string, request, response []byte) {
	if len(m.Logs) == cap(m.Logs) {
		<-m.Logs
	}

	m.Logs <- errorLog{url, request, response}
}

// Execute will do a json call to multisafepay
// payload represents the request body that will be encoded as json
// the returnVal will be transformed the same way json.Unmarshal does.
// If an error occurs an APIError will be returned
func (m *MultiSafePay) Execute(url *url.URL, method string, payload interface{}, returnVal interface{}) *APIError {
	requestBody := &bytes.Buffer{}
	if payload != nil {
		encoder := json.NewEncoder(requestBody)
		err := encoder.Encode(payload)

		if err != nil {
			return errorToAPIError(err)
		}
	}

	res, err := m.Call(url, method, requestBody)
	if err != nil {
		return errorToAPIError(err)
	}

	// Copy the response body in buffer to re-read the contents
	buff := &bytes.Buffer{}
	io.Copy(buff, res.Body)
	res.Body.Close()

	// If we get a non 200 status code try to parse the response into APIError
	if res.StatusCode != http.StatusOK {
		var apiErr *APIError

		// unmarshall the response and check if we have an error
		if err = json.Unmarshal(buff.Bytes(), &apiErr); err == nil && apiErr.Code != 0 {
			return apiErr
		}

		return errorToAPIError(fmt.Errorf("Expected status code %d got %d but could not parse an ApiErr for the response body: %s", http.StatusOK, res.StatusCode, string(buff.Bytes())))
	}

	var checkSuccess struct {
		Success bool            `json:"success"`
		Data    json.RawMessage `json:"data"`
	}

	err = json.Unmarshal(buff.Bytes(), &checkSuccess)
	if err != nil {
		return errorToAPIError(fmt.Errorf("Could not parse response: %s into:%s got error:", string(buff.Bytes()), reflect.TypeOf(returnVal).Name(), err))
	}

	if checkSuccess.Success == false {
		return errorToAPIError(fmt.Errorf("The response returned a 200 OK but contained a success=false for response: %s", string(buff.Bytes())))
	}

	if returnVal != nil {
		err = json.Unmarshal(checkSuccess.Data, returnVal)
		if err != nil {
			return errorToAPIError(fmt.Errorf("Could not parse data field: %s into:%s got error:", string(buff.Bytes()), reflect.TypeOf(returnVal).Name(), err))
		}
	}

	return nil
}

// Call can be used to get the raw unparsed http.Response.
// Remember to close the response body when using this function.
func (m *MultiSafePay) Call(url *url.URL, method string, payload io.Reader) (*http.Response, error) {

	req, err := http.NewRequest(method, url.String(), payload)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header["api_key"] = []string{m.apiKey}

	var requestDump []byte
	if m.debug {
		requestDump, _ = httputil.DumpRequest(req, true)
	}

	client := &http.Client{}
	res, err := client.Do(req)

	if err != nil {
		return nil, err
	}

	req.Body.Close()

	if m.debug {
		b, _ := httputil.DumpResponse(res, true)
		m.log(url.String(), requestDump, b)
	}

	return res, err
}
<commit_msg>log to stdout instead of a channel<commit_after>package multisafego

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"reflect"
)

var (
	// TESTURL is the url to use in test mode
	TESTURL *url.URL
	// PRODURL is the url to use in production mode
	PRODURL *url.URL
)

func init() {
	TESTURL, _ = url.Parse("https://testapi.multisafepay.com")
	PRODURL, _ = url.Parse("https://api.multisafepay.com")
}

// MultiSafePay is the wrapper for calls to the Api.
// Use the New function to construct a basic variant
type MultiSafePay struct {
	apiKey  string
	baseURL *url.URL
	debug   bool
}

// A APIError occurs when the response has a non 200 status code
// default error values like a timeout will be wrapped in an APIError with code -1
type APIError struct {
	Code    int         `json:"error_code"`
	Message string      `json:"error_info"`
	Data    interface{} `json:"data"`
}

func (a APIError) String() string {
	return fmt.Sprintf("[%d] %s", a.Code, a.Message)
}

type errorLog struct {
	URL      string
	Request  []byte
	Response []byte
}

// New returns a basic multisafepay object that can be set in debug mode to dump requests/responses
func New(apiKey string, baseURL *url.URL, debug bool) *MultiSafePay {
	return &MultiSafePay{apiKey, baseURL, debug}
}

func errorToAPIError(err error) *APIError {
	return &APIError{-1, err.Error(), nil}
}

// Path is a shortcut for prepending /v1/json to the path
func Path(path string) string {
	return "/v1/json" + path
}

func (m *MultiSafePay) log(url string, request, response []byte) {
	log.Printf("[%s]\nRequest:%s\nResponse:%s\n", url, string(request), string(response))
}

// Execute will do a json call to multisafepay
// payload represents the request body that will be encoded as json
// the returnVal will be transformed the same way json.Unmarshal does.
// If an error occurs an APIError will be returned
func (m *MultiSafePay) Execute(url *url.URL, method string, payload interface{}, returnVal interface{}) *APIError {
	requestBody := &bytes.Buffer{}
	if payload != nil {
		encoder := json.NewEncoder(requestBody)
		err := encoder.Encode(payload)

		if err != nil {
			return errorToAPIError(err)
		}
	}

	res, err := m.Call(url, method, requestBody)
	if err != nil {
		return errorToAPIError(err)
	}

	// Copy the response body in buffer to re-read the contents
	buff := &bytes.Buffer{}
	io.Copy(buff, res.Body)
	res.Body.Close()

	// If we get a non 200 status code try to parse the response into APIError
	if res.StatusCode != http.StatusOK {
		var apiErr *APIError

		// unmarshall the response and check if we have an error
		if err = json.Unmarshal(buff.Bytes(), &apiErr); err == nil && apiErr.Code != 0 {
			return apiErr
		}

		return errorToAPIError(fmt.Errorf("Expected status code %d got %d but could not parse an ApiErr for the response body: %s", http.StatusOK, res.StatusCode, string(buff.Bytes())))
	}

	var checkSuccess struct {
		Success bool            `json:"success"`
		Data    json.RawMessage `json:"data"`
	}

	err = json.Unmarshal(buff.Bytes(), &checkSuccess)
	if err != nil {
		return errorToAPIError(fmt.Errorf("Could not parse response: %s into:%s got error:", string(buff.Bytes()), reflect.TypeOf(returnVal).Name(), err))
	}

	if checkSuccess.Success == false {
		return errorToAPIError(fmt.Errorf("The response returned a 200 OK but contained a success=false for response: %s", string(buff.Bytes())))
	}

	if returnVal != nil {
		err = json.Unmarshal(checkSuccess.Data, returnVal)
		if err != nil {
			return errorToAPIError(fmt.Errorf("Could not parse data field: %s into:%s got error:", string(buff.Bytes()), reflect.TypeOf(returnVal).Name(), err))
		}
	}

	return nil
}

// Call can be used to get the raw unparsed http.Response.
// Remember to close the response body when using this function.
func (m *MultiSafePay) Call(url *url.URL, method string, payload io.Reader) (*http.Response, error) {

	req, err := http.NewRequest(method, url.String(), payload)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header["api_key"] = []string{m.apiKey}

	var requestDump []byte
	if m.debug {
		requestDump, _ = httputil.DumpRequest(req, true)
	}

	client := &http.Client{}
	res, err := client.Do(req)

	if err != nil {
		return nil, err
	}

	req.Body.Close()

	if m.debug {
		b, _ := httputil.DumpResponse(res, true)
		m.log(url.String(), requestDump, b)
	}

	return res, err
}
<|endoftext|>


<commit_before>package harvest

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"

	"github.com/pkg/errors"
)

const CLIENT_VERSION = "1.0"
const HARVEST_DOMAIN = "api.harvestapp.com"
const HARVEST_API_VERSION = "v2"

type API struct {
	client       *http.Client
	BaseURL      string
	AccountID    string
	AccessToken  string
	RefreshToken string
	UserAgent    string
}

func NewTokenAPI(accountID string, accessToken string) *API {
	a := API{}
	a.client = http.DefaultClient
	a.BaseURL = "https://" + HARVEST_DOMAIN + "/" + HARVEST_API_VERSION
	a.AccountID = accountID
	a.AccessToken = accessToken
	return &a
}

func (a *API) GetPaginated(path string, args Arguments, target Pageable, afterFetch func()) error {
	page := 1
	args["page"] = fmt.Sprintf("%d", page)
	err := a.Get(path, args, target)
	if err != nil {
		return err
	}

	afterFetch()

	for target.HasNextPage() {
		page++
		args["page"] = fmt.Sprintf("%d", page)
		err = a.Get(path, args, target)
		if err != nil {
			return err
		}
		afterFetch()
	}
	return nil
}

func (a *API) Get(path string, args Arguments, target interface{}) error {
	url := fmt.Sprintf("%s%s", a.BaseURL, path)
	urlWithParams := fmt.Sprintf("%s?%s", url, args.ToURLValues().Encode())

	req, err := http.NewRequest("GET", urlWithParams, nil)
	if err != nil {
		return errors.Wrapf(err, "Invalid GET request %s", url)
	}
	a.AddHeaders(req)

	resp, err := a.client.Do(req)
	if err != nil {
		return errors.Wrapf(err, "HTTP request failure on %s", url)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		var body []byte
		body, err = ioutil.ReadAll(resp.Body)
		return errors.Errorf("HTTP request failure on %s: %s %s", url, string(body), err)
	}

	decoder := json.NewDecoder(resp.Body)
	err = decoder.Decode(target)
	if err != nil {
		body, _ := ioutil.ReadAll(resp.Body)
		return errors.Wrapf(err, "JSON decode failed on %s: %s", url, string(body))
	}

	return nil
}

func (a *API) Put(path string, args Arguments, postData interface{}, target interface{}) error {
	url := fmt.Sprintf("%s%s", a.BaseURL, path)
	urlWithParams := fmt.Sprintf("%s?%s", url, args.ToURLValues().Encode())

	buffer := new(bytes.Buffer)
	if postData != nil {
		json.NewEncoder(buffer).Encode(postData)
	}

	req, err := http.NewRequest("PUT", urlWithParams, buffer)
	if err != nil {
		return errors.Wrapf(err, "Invalid PUT request %s", url)
	}
	a.AddHeaders(req)
	req.Header.Set("Content-Type", "application/json; charset=utf-8")

	resp, err := a.client.Do(req)
	if err != nil {
		return errors.Wrapf(err, "HTTP request failure on %s", url)
	}
	defer resp.Body.Close()
	if resp.StatusCode < 200 || resp.StatusCode > 299 {
		var body []byte
		body, err = ioutil.ReadAll(resp.Body)
		return errors.Wrapf(err, "HTTP request failure on %s: %s %s", url, string(body), err)
	}

	// Harvest V1 API returns an empty response, with a Location header including the
	// URI of the created object (e.g. /projects/254454)
	redirectDestination := resp.Header.Get("Location")
	if redirectDestination != "" {
		return a.Get(redirectDestination, args, target)
	} else {
		decoder := json.NewDecoder(resp.Body)
		err = decoder.Decode(target)
		if err != nil {
			body, _ := ioutil.ReadAll(resp.Body)
			return errors.Wrapf(err, "JSON decode failed on %s: %s", url, string(body))
		}
	}

	return nil
}

func (a *API) Post(path string, args Arguments, postData interface{}, target interface{}) error {
	url := fmt.Sprintf("%s%s", a.BaseURL, path)
	urlWithParams := fmt.Sprintf("%s?%s", url, args.ToURLValues().Encode())

	buffer := new(bytes.Buffer)
	if postData != nil {
		json.NewEncoder(buffer).Encode(postData)
	}

	req, err := http.NewRequest("POST", urlWithParams, buffer)
	if err != nil {
		return errors.Wrapf(err, "Invalid POST request %s", url)
	}
	a.AddHeaders(req)
	req.Header.Set("Content-Type", "application/json; charset=utf-8")

	resp, err := a.client.Do(req)
	if err != nil {
		return errors.Wrapf(err, "HTTP request failure on %s", url)
	}
	defer resp.Body.Close()
	if resp.StatusCode < 200 || resp.StatusCode > 299 {
		var body []byte
		body, err = ioutil.ReadAll(resp.Body)
		return errors.Wrapf(err, "HTTP request failure on %s: %s %s", url, string(body), err)
	}

	// Harvest V1 API returns an empty response, with a Location header including the
	// URI of the created object (e.g. /projects/254454)
	redirectDestination := resp.Header.Get("Location")
	if redirectDestination != "" {
		return a.Get(redirectDestination, args, target)
	} else {
		return errors.Errorf("POST to %s failed to return a Location header. This means we couldn't fetch the new state of the record.", url)
	}

	return nil
}

func (a *API) Delete(path string, args Arguments) error {
	url := fmt.Sprintf("%s%s", a.BaseURL, path)
	urlWithParams := fmt.Sprintf("%s?%s", url, args.ToURLValues().Encode())

	req, err := http.NewRequest("DELETE", urlWithParams, nil)
	if err != nil {
		return errors.Wrapf(err, "Invalid DELETE request %s", url)
	}
	a.AddHeaders(req)

	resp, err := a.client.Do(req)
	if err != nil {
		return errors.Wrapf(err, "HTTP request failure on %s", url)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		var body []byte
		body, err = ioutil.ReadAll(resp.Body)
		return errors.Wrapf(err, "HTTP request failure on %s: %s %s", url, string(body), err)
	}

	return nil
}

// Applies relevant User-Agent, Accept, Authorization headers
func (a *API) AddHeaders(req *http.Request) {
	req.Header.Set("Accept", "application/json")

	if a.UserAgent != "" {
		req.Header.Set("User-Agent", a.UserAgent)
	} else {
		req.Header.Set("User-Agent", defaultUserAgent())
	}

	if a.AccountID != "" {
		req.Header.Set("Harvest-Account-Id", a.AccountID)
	}

	if a.AccessToken != "" {
		req.Header.Set("Authorization", "Bearer "+a.AccessToken)
	}
}

func defaultUserAgent() string {
	return "github.com/adlio/harvest v" + CLIENT_VERSION
}
<commit_msg>Adds an API.Logger field to allow plugging in a debug logger.<commit_after>package harvest

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"

	"github.com/pkg/errors"
)

const CLIENT_VERSION = "1.0"
const HARVEST_DOMAIN = "api.harvestapp.com"
const HARVEST_API_VERSION = "v2"

type API struct {
	client       *http.Client
	Logger       logger
	BaseURL      string
	AccountID    string
	AccessToken  string
	RefreshToken string
	UserAgent    string
}

type logger interface {
	Debugf(string, ...interface{})
}

func NewTokenAPI(accountID string, accessToken string) *API {
	a := API{}
	a.client = http.DefaultClient
	a.BaseURL = "https://" + HARVEST_DOMAIN + "/" + HARVEST_API_VERSION
	a.AccountID = accountID
	a.AccessToken = accessToken
	return &a
}

func (a *API) GetPaginated(path string, args Arguments, target Pageable, afterFetch func()) error {
	page := 1
	args["page"] = fmt.Sprintf("%d", page)
	err := a.Get(path, args, target)
	if err != nil {
		return err
	}

	afterFetch()

	for target.HasNextPage() {
		page++
		args["page"] = fmt.Sprintf("%d", page)
		err = a.Get(path, args, target)
		if err != nil {
			return err
		}
		afterFetch()
	}
	return nil
}

func (a *API) Get(path string, args Arguments, target interface{}) error {
	url := fmt.Sprintf("%s%s", a.BaseURL, path)
	urlWithParams := fmt.Sprintf("%s?%s", url, args.ToURLValues().Encode())

	req, err := http.NewRequest("GET", urlWithParams, nil)
	if err != nil {
		return errors.Wrapf(err, "Invalid GET request %s", url)
	}
	a.AddHeaders(req)

	resp, err := a.client.Do(req)
	if err != nil {
		return errors.Wrapf(err, "HTTP request failure on %s", url)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		var body []byte
		body, err = ioutil.ReadAll(resp.Body)
		return errors.Errorf("HTTP request failure on %s: %s %s", url, string(body), err)
	}

	decoder := json.NewDecoder(resp.Body)
	err = decoder.Decode(target)
	if err != nil {
		body, _ := ioutil.ReadAll(resp.Body)
		return errors.Wrapf(err, "JSON decode failed on %s: %s", url, string(body))
	}

	return nil
}

func (a *API) Put(path string, args Arguments, postData interface{}, target interface{}) error {
	url := fmt.Sprintf("%s%s", a.BaseURL, path)
	urlWithParams := fmt.Sprintf("%s?%s", url, args.ToURLValues().Encode())

	buffer := new(bytes.Buffer)
	if postData != nil {
		json.NewEncoder(buffer).Encode(postData)
	}

	req, err := http.NewRequest("PUT", urlWithParams, buffer)
	if err != nil {
		return errors.Wrapf(err, "Invalid PUT request %s", url)
	}
	a.AddHeaders(req)
	req.Header.Set("Content-Type", "application/json; charset=utf-8")

	resp, err := a.client.Do(req)
	if err != nil {
		return errors.Wrapf(err, "HTTP request failure on %s", url)
	}
	defer resp.Body.Close()
	if resp.StatusCode < 200 || resp.StatusCode > 299 {
		var body []byte
		body, err = ioutil.ReadAll(resp.Body)
		return errors.Wrapf(err, "HTTP request failure on %s: %s %s", url, string(body), err)
	}

	// Harvest V1 API returns an empty response, with a Location header including the
	// URI of the created object (e.g. /projects/254454)
	redirectDestination := resp.Header.Get("Location")
	if redirectDestination != "" {
		return a.Get(redirectDestination, args, target)
	} else {
		decoder := json.NewDecoder(resp.Body)
		err = decoder.Decode(target)
		if err != nil {
			body, _ := ioutil.ReadAll(resp.Body)
			return errors.Wrapf(err, "JSON decode failed on %s: %s", url, string(body))
		}
	}

	return nil
}

func (a *API) Post(path string, args Arguments, postData interface{}, target interface{}) error {
	url := fmt.Sprintf("%s%s", a.BaseURL, path)
	urlWithParams := fmt.Sprintf("%s?%s", url, args.ToURLValues().Encode())

	buffer := new(bytes.Buffer)
	if postData != nil {
		json.NewEncoder(buffer).Encode(postData)
	}

	req, err := http.NewRequest("POST", urlWithParams, buffer)
	if err != nil {
		return errors.Wrapf(err, "Invalid POST request %s", url)
	}
	a.AddHeaders(req)
	req.Header.Set("Content-Type", "application/json; charset=utf-8")

	resp, err := a.client.Do(req)
	if err != nil {
		return errors.Wrapf(err, "HTTP request failure on %s", url)
	}
	defer resp.Body.Close()
	if resp.StatusCode < 200 || resp.StatusCode > 299 {
		var body []byte
		body, err = ioutil.ReadAll(resp.Body)
		return errors.Wrapf(err, "HTTP request failure on %s: %s %s", url, string(body), err)
	}

	// Harvest V1 API returns an empty response, with a Location header including the
	// URI of the created object (e.g. /projects/254454)
	redirectDestination := resp.Header.Get("Location")
	if redirectDestination != "" {
		return a.Get(redirectDestination, args, target)
	} else {
		return errors.Errorf("POST to %s failed to return a Location header. This means we couldn't fetch the new state of the record.", url)
	}

	return nil
}

func (a *API) Delete(path string, args Arguments) error {
	url := fmt.Sprintf("%s%s", a.BaseURL, path)
	urlWithParams := fmt.Sprintf("%s?%s", url, args.ToURLValues().Encode())

	req, err := http.NewRequest("DELETE", urlWithParams, nil)
	if err != nil {
		return errors.Wrapf(err, "Invalid DELETE request %s", url)
	}
	a.AddHeaders(req)

	resp, err := a.client.Do(req)
	if err != nil {
		return errors.Wrapf(err, "HTTP request failure on %s", url)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		var body []byte
		body, err = ioutil.ReadAll(resp.Body)
		return errors.Wrapf(err, "HTTP request failure on %s: %s %s", url, string(body), err)
	}

	return nil
}

// Applies relevant User-Agent, Accept, Authorization headers
func (a *API) AddHeaders(req *http.Request) {
	req.Header.Set("Accept", "application/json")

	if a.UserAgent != "" {
		req.Header.Set("User-Agent", a.UserAgent)
	} else {
		req.Header.Set("User-Agent", defaultUserAgent())
	}

	if a.AccountID != "" {
		req.Header.Set("Harvest-Account-Id", a.AccountID)
	}

	if a.AccessToken != "" {
		req.Header.Set("Authorization", "Bearer "+a.AccessToken)
	}
}

func (a *API) log(format string, args ...interface{}) {
	if a.Logger != nil {
		a.Logger.Debugf(format, args)
	}
}

func defaultUserAgent() string {
	return "github.com/adlio/harvest v" + CLIENT_VERSION
}
<|endoftext|>


<commit_before>package hugoreview

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"

	"github.com/fsouza/go-dockerclient"
)

type Repository struct {
	CloneURL string `json:"clone_url"`
}

type PullRequestEvent struct {
	Action string `json:"action"`
	Number int    `json:"number"`

	Repository Repository `json:"repository"`
}

type PullRequestHandler struct {
	Secret string
	Domain string
	Port   string

	ContainerHandler *ContainerHandler
}

func (h *PullRequestHandler) formatHost(number int) string {
	return fmt.Sprintf("pr-%d.%s", number, h.Domain)
}

func (h *PullRequestHandler) close(payload *PullRequestEvent) error {
	host := h.formatHost(payload.Number)
	httphost := host + ":" + h.Port
	c := h.ContainerHandler.Store.GetHost(httphost)
	err := h.ContainerHandler.Destroy(c)
	if err != nil {
		return err
	}
	return h.ContainerHandler.Deregister(httphost)
}

func (h *PullRequestHandler) open(payload *PullRequestEvent) error {
	env := []string{
		fmt.Sprintf("GIT_REPO=%s", payload.Repository.CloneURL),
		fmt.Sprintf("GITHUB_PR_NUMBER=%d", payload.Number),
	}
	host := h.formatHost(payload.Number)
	c, err := h.ContainerHandler.Run(host, h.Port, env)
	if err != nil {
		return err
	}
	return h.ContainerHandler.Register(host+":"+h.Port, c)
}

func (h *PullRequestHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	if req.Header.Get("X-Github-Event") != "pull_request" {
		http.Error(rw, "Bad Request", http.StatusBadRequest)
		return
	}

	payload := &PullRequestEvent{}
	defer req.Body.Close()
	if err := json.NewDecoder(req.Body).Decode(payload); err != nil {
		http.Error(rw, err.Error(), http.StatusBadRequest)
		return
	}

	log.Print(*payload)

	switch payload.Action {
	case "opened", "reopened":
		if err := h.open(payload); err != nil {
			http.Error(rw, err.Error(), http.StatusBadRequest)
			return
		}
	case "synchronize":
		// destroy the container
		// launch a new container
		// register its IPAddr:Port to redis
	case "closed":
		// destroy the container
		if err := h.close(payload); err != nil {
			http.Error(rw, err.Error(), http.StatusBadRequest)
			return
		}
	default:
		http.Error(rw, "Bad Request", http.StatusBadRequest)
		return
	}
}

// ContainerHandler manages a container by key
type ContainerHandler struct {
	*docker.Client

	Store Store
	Image string
}

// Run creates new container and starts the container.
func (h *ContainerHandler) Run(host, port string, env []string) (*Container, error) {
	env = append(env, "PORT="+port)
	opts := docker.CreateContainerOptions{
		Config: &docker.Config{
			Image:    h.Image,
			Hostname: host,
			Env:      env,
		},
	}

	c, err := h.CreateContainer(opts)
	if err != nil {
		return nil, err
	}

	if err := h.StartContainer(c.ID, &docker.HostConfig{}); err != nil {
		return nil, err
	}

	c, err = h.InspectContainer(c.ID)
	if err != nil {
		return nil, err
	}

	return &Container{
		ID:   c.ID,
		Host: c.NetworkSettings.IPAddress + ":" + port,
	}, nil
}

// Destroy destroys a container pointed by c
func (h *ContainerHandler) Destroy(c *Container) error {
	opts := docker.RemoveContainerOptions{
		ID:            c.ID,
		RemoveVolumes: true,
		Force:         true,
	}
	return h.RemoveContainer(opts)
}

// Register registers key with c to Store
func (h *ContainerHandler) Register(host string, c *Container) error {
	return h.Store.SetHost(host, c)
}

// Deregister removes key from Store
func (h *ContainerHandler) Deregister(key string) error {
	return h.Store.DeleteHost(key)
}
<commit_msg>api: Add ping handler<commit_after>package hugoreview

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"

	"github.com/fsouza/go-dockerclient"
)

type Repository struct {
	CloneURL string `json:"clone_url"`
}

type PullRequestEvent struct {
	Action string `json:"action"`
	Number int    `json:"number"`

	Repository Repository `json:"repository"`
}

type PullRequestHandler struct {
	Secret string
	Domain string
	Port   string

	ContainerHandler *ContainerHandler
}

func (h *PullRequestHandler) formatHost(number int) string {
	return fmt.Sprintf("pr-%d.%s", number, h.Domain)
}

func (h *PullRequestHandler) close(payload *PullRequestEvent) error {
	host := h.formatHost(payload.Number)
	httphost := host + ":" + h.Port
	c := h.ContainerHandler.Store.GetHost(httphost)
	err := h.ContainerHandler.Destroy(c)
	if err != nil {
		return err
	}
	return h.ContainerHandler.Deregister(httphost)
}

func (h *PullRequestHandler) open(payload *PullRequestEvent) error {
	env := []string{
		fmt.Sprintf("GIT_REPO=%s", payload.Repository.CloneURL),
		fmt.Sprintf("GITHUB_PR_NUMBER=%d", payload.Number),
	}
	host := h.formatHost(payload.Number)
	c, err := h.ContainerHandler.Run(host, h.Port, env)
	if err != nil {
		return err
	}
	return h.ContainerHandler.Register(host+":"+h.Port, c)
}

func (h *PullRequestHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	if req.Header.Get("X-Github-Event") != "ping" {
		log.Print("Ping")
		io.Copy(rw, req.Body)
		return
	}

	if req.Header.Get("X-Github-Event") != "pull_request" {
		http.Error(rw, "Bad Request", http.StatusBadRequest)
		return
	}

	payload := &PullRequestEvent{}
	defer req.Body.Close()
	if err := json.NewDecoder(req.Body).Decode(payload); err != nil {
		http.Error(rw, err.Error(), http.StatusBadRequest)
		return
	}

	log.Print(*payload)

	switch payload.Action {
	case "opened", "reopened":
		if err := h.open(payload); err != nil {
			http.Error(rw, err.Error(), http.StatusBadRequest)
			return
		}
	case "synchronize":
		// destroy the container
		// launch a new container
		// register its IPAddr:Port to redis
	case "closed":
		// destroy the container
		if err := h.close(payload); err != nil {
			http.Error(rw, err.Error(), http.StatusBadRequest)
			return
		}
	default:
		http.Error(rw, "Bad Request", http.StatusBadRequest)
		return
	}
}

// ContainerHandler manages a container by key
type ContainerHandler struct {
	*docker.Client

	Store Store
	Image string
}

// Run creates new container and starts the container.
func (h *ContainerHandler) Run(host, port string, env []string) (*Container, error) {
	env = append(env, "PORT="+port)
	opts := docker.CreateContainerOptions{
		Config: &docker.Config{
			Image:    h.Image,
			Hostname: host,
			Env:      env,
		},
	}

	c, err := h.CreateContainer(opts)
	if err != nil {
		return nil, err
	}

	if err := h.StartContainer(c.ID, &docker.HostConfig{}); err != nil {
		return nil, err
	}

	c, err = h.InspectContainer(c.ID)
	if err != nil {
		return nil, err
	}

	return &Container{
		ID:   c.ID,
		Host: c.NetworkSettings.IPAddress + ":" + port,
	}, nil
}

// Destroy destroys a container pointed by c
func (h *ContainerHandler) Destroy(c *Container) error {
	opts := docker.RemoveContainerOptions{
		ID:            c.ID,
		RemoveVolumes: true,
		Force:         true,
	}
	return h.RemoveContainer(opts)
}

// Register registers key with c to Store
func (h *ContainerHandler) Register(host string, c *Container) error {
	return h.Store.SetHost(host, c)
}

// Deregister removes key from Store
func (h *ContainerHandler) Deregister(key string) error {
	return h.Store.DeleteHost(key)
}
<|endoftext|>


<commit_before>// Copyright 2014 Vadim Kravcenko
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package gopencils

import (
	"crypto/tls"
	"net/http"
	"net/http/cookiejar"
	"net/url"
)

// Basic Auth
type BasicAuth struct {
	Username string
	Password string
}

// Main Api Instance.
// No Options yet supported.
type ApiStruct struct {
	BaseUrl   *url.URL
	BasicAuth *BasicAuth
	Client    *http.Client
	Cookies   *cookiejar.Jar
}

// Create a new API Instance and returns a Resource
// Accepts URL as parameter, and either a Basic Auth or a OAuth2 Client.
func Api(baseUrl string, options ...interface{}) *Resource {
	u, err := url.Parse(baseUrl)
	if err != nil {
		// TODO: don't panic..
		panic("Api() - url.Parse(baseUrl) Error:" + err.Error())
	}

	apiInstance := &ApiStruct{BaseUrl: u, BasicAuth: nil}

	for _, o := range options {
		if auth, ok := o.(*BasicAuth); ok {
			apiInstance.BasicAuth = auth
		}
		if oauthClient, ok := o.(*http.Client); ok {
			apiInstance.Client = oauthClient
		}
	}

	if apiInstance.Client == nil {
		apiInstance.Cookies, _ = cookiejar.New(nil)

		// Skip verify by default?
		tr := &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}

		client := &http.Client{
			Transport: tr,
			Jar:       apiInstance.Cookies,
		}
		apiInstance.Client = client
	}
	return &Resource{Url: "", Api: apiInstance}
}
<commit_msg>change options iteration to type switch<commit_after>// Copyright 2014 Vadim Kravcenko
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package gopencils

import (
	"crypto/tls"
	"net/http"
	"net/http/cookiejar"
	"net/url"
)

// Basic Auth
type BasicAuth struct {
	Username string
	Password string
}

// Main Api Instance.
// No Options yet supported.
type ApiStruct struct {
	BaseUrl   *url.URL
	BasicAuth *BasicAuth
	Client    *http.Client
	Cookies   *cookiejar.Jar
}

// Create a new API Instance and returns a Resource
// Accepts URL as parameter, and either a Basic Auth or a OAuth2 Client.
func Api(baseUrl string, options ...interface{}) *Resource {
	u, err := url.Parse(baseUrl)
	if err != nil {
		// TODO: don't panic..
		panic("Api() - url.Parse(baseUrl) Error:" + err.Error())
	}

	apiInstance := &ApiStruct{BaseUrl: u, BasicAuth: nil}

	for _, o := range options {
		switch v := o.(type) {
		case *BasicAuth:
			apiInstance.BasicAuth = v
		case *http.Client:
			apiInstance.Client = v
		}
	}

	if apiInstance.Client == nil {
		apiInstance.Cookies, _ = cookiejar.New(nil)

		// Skip verify by default?
		tr := &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}

		client := &http.Client{
			Transport: tr,
			Jar:       apiInstance.Cookies,
		}
		apiInstance.Client = client
	}
	return &Resource{Url: "", Api: apiInstance}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/ant0ine/go-json-rest/rest"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
)

/* Editable structure */
type Editable struct {
	FilePath string `json:"filepath"`
	Content  string `json:"content"`
}

/* Project directory for editing */
var projectDir string = "/Users/ktt/w/webplug/project/"

/* List of Project files */
var projectFiles []string

/* Directory walking recursive function */
func collectProjectFiles(fp string, fi os.FileInfo, err error) error {
	if err != nil {
		log.Println(err.Error())
		return nil // Can't walk here, move on
	}
	if !!fi.IsDir() {
		return nil //not a file. Ignore.
	}

	matched, err := filepath.Match("*.*", fi.Name())
	hmatched, _ := filepath.Match(".*", fi.Name())
	if err != nil {
		return err //malform pattern, failed.
	}
	if matched && !hmatched {
		relPath, _ := filepath.Rel(projectDir, fp)   // extract relative path
		projectFiles = append(projectFiles, relPath) //collect
	}
	return nil
}

/* API: This returns the list of all files in project directory including files in
sub directory
> curl -i http://127.0.0.1:3000/api/files
*/
func FileTreeHandler(w rest.ResponseWriter, r *rest.Request) {
	projectFiles = make([]string, 0)
	filepath.Walk(projectDir, collectProjectFiles) // populate projectFiles
	w.WriteJson(map[string][]string{
		"files": projectFiles,
	})
}

/* API: This returns the file content of given file in project directory
> curl -i http://127.0.0.1:3000/api/files/open?t=
*/
func FileOpenHandler(w rest.ResponseWriter, r *rest.Request) {
	r.ParseForm()
	target := r.Form.Get("target")

	if target == "" {
		rest.Error(w, "Missing target file.", http.StatusBadRequest)
		return
	}

	content, err := ioutil.ReadFile(projectDir + target)
	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.WriteJson(&Editable{target, fmt.Sprintf("%s", content)})
}

/* API: This write given content to given file in project directory */
func FileSaveHandler(w rest.ResponseWriter, r *rest.Request) {
	w.WriteJson(map[string]string{
		"Body": "FileSaveHandler",
	})
}

/* API: This simply create the needed directories & file */
func FileNewHandler(w rest.ResponseWriter, r *rest.Request) {
	w.WriteJson(map[string]string{
		"Body": "FileNewHandler",
	})
}

func main() {

	/* API Router setup */
	api := rest.NewApi()
	api.Use(&rest.AccessLogApacheMiddleware{})
	api.Use(rest.DefaultCommonStack...)
	apiRouter, _ := rest.MakeRouter(
		rest.Get("/files", FileTreeHandler),
		rest.Get("/files/open", FileOpenHandler),
		rest.Post("/files/save", FileSaveHandler),
		rest.Post("/files/new", FileNewHandler),
	)
	api.SetApp(apiRouter)

	/* API endpoints */
	http.Handle("/api/", http.StripPrefix("/api", api.MakeHandler()))

	/* AngularJS App Distribution Directory */
	http.Handle("/", http.FileServer(http.Dir("./site_app/dist")))

	/* Now start the server */
	log.Fatal(http.ListenAndServe(":3000", nil))
}
<commit_msg>File save API is implemented.<commit_after>package main

import (
	"fmt"
	"github.com/ant0ine/go-json-rest/rest"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
)

/* Editable structure */
type Editable struct {
	FilePath string `json:"filepath"`
	Content  string `json:"content"`
}

/* Project directory for editing */
var projectDir string = "/Users/ktt/w/webplug/project/"

/* List of Project files */
var projectFiles []string

/* Directory walking recursive function */
func collectProjectFiles(fp string, fi os.FileInfo, err error) error {
	if err != nil {
		log.Println(err.Error())
		return nil // Can't walk here, move on
	}
	if !!fi.IsDir() {
		return nil //not a file. Ignore.
	}

	matched, err := filepath.Match("*.*", fi.Name())
	hmatched, _ := filepath.Match(".*", fi.Name())
	if err != nil {
		return err //malform pattern, failed.
	}
	if matched && !hmatched {
		relPath, _ := filepath.Rel(projectDir, fp)   // extract relative path
		projectFiles = append(projectFiles, relPath) //collect
	}
	return nil
}

/* API: This returns the list of all files in project directory including files in
sub directory
> curl -i http://127.0.0.1:3000/api/files
*/
func FileTreeHandler(w rest.ResponseWriter, r *rest.Request) {
	projectFiles = make([]string, 0)
	filepath.Walk(projectDir, collectProjectFiles) // populate projectFiles
	w.WriteJson(map[string][]string{
		"files": projectFiles,
	})
}

/* API: This returns the file content of given file in project directory
> curl -i http://127.0.0.1:3000/api/files/open?t=
*/
func FileOpenHandler(w rest.ResponseWriter, r *rest.Request) {
	r.ParseForm()
	target := r.Form.Get("target")

	if target == "" {
		rest.Error(w, "Missing target file.", http.StatusBadRequest)
		return
	}

	content, err := ioutil.ReadFile(projectDir + target)
	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.WriteJson(&Editable{target, fmt.Sprintf("%s", content)})
}

/* API: This write given content to given file in project directory */
func FileSaveHandler(w rest.ResponseWriter, r *rest.Request) {
	// Get Payload
	editable := Editable{}
	err := r.DecodeJsonPayload(&editable)
	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// Write file and check error
	err = ioutil.WriteFile(projectDir+editable.FilePath, []byte(editable.Content), 0644)
	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusCreated)
}

/* API: This simply create the needed directories & file */
func FileNewHandler(w rest.ResponseWriter, r *rest.Request) {
	w.WriteJson(map[string]string{
		"Body": "FileNewHandler",
	})
}

func main() {

	/* API Router setup */
	api := rest.NewApi()
	api.Use(&rest.AccessLogApacheMiddleware{})
	api.Use(rest.DefaultCommonStack...)
	apiRouter, _ := rest.MakeRouter(
		rest.Get("/files", FileTreeHandler),
		rest.Get("/files/open", FileOpenHandler),
		rest.Post("/files/save", FileSaveHandler),
		rest.Post("/files/new", FileNewHandler),
	)
	api.SetApp(apiRouter)

	/* API endpoints */
	http.Handle("/api/", http.StripPrefix("/api", api.MakeHandler()))

	/* AngularJS App Distribution Directory */
	http.Handle("/", http.FileServer(http.Dir("./site_app/dist")))

	/* Now start the server */
	log.Fatal(http.ListenAndServe(":3000", nil))
}
<|endoftext|>


<commit_before>package gogist

import (
	"fmt"
	"net/http"

	"github.com/gorilla/mux"
)

const t = `
<html>
  <head>
    <meta name="go-import" content="%s git https://gist.github.com/%s.git" />
    <script>window.location='https://github.com/ImJasonH/go-gist/';</script>
  </head>
</html>
`

func init() {
	r := mux.NewRouter()
	h := func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "text/html")
		w.Write([]byte(fmt.Sprintf(t, r.URL.Host+r.URL.Path, mux.Vars(r)["gistID"])))
	}
	r.HandleFunc("/{username}/{gistID:[0-9]+}", h).Methods("GET")
	r.HandleFunc("/{username}/{gistID:[0-9]+}/{package:[a-zA-Z0-9]+}", h).Methods("GET")
	r.HandleFunc("/{gistID:[0-9]+}", h).Methods("GET")
	r.HandleFunc("/{gistID:[0-9]+}/{package:[a-zA-Z0-9]+}", h).Methods("GET")
	r.Handle("/", http.RedirectHandler("https://github.com/ImJasonH/go-gist", http.StatusSeeOther))
	http.Handle("/", r)
}
<commit_msg>Reorder route ordering for great justice<commit_after>package gogist

import (
	"fmt"
	"net/http"

	"github.com/gorilla/mux"
)

const t = `
<html>
  <head>
    <meta name="go-import" content="%s git https://gist.github.com/%s.git" />
    <script>window.location='https://github.com/ImJasonH/go-gist/';</script>
  </head>
</html>
`

func init() {
	r := mux.NewRouter()
	h := func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "text/html")
		w.Write([]byte(fmt.Sprintf(t, r.URL.Host+r.URL.Path, mux.Vars(r)["gistID"])))
	}
	r.HandleFunc("/{username}/{gistID:[0-9]+}/{package:[a-zA-Z0-9]+}", h).Methods("GET")
	r.HandleFunc("/{username}/{gistID:[0-9]+}", h).Methods("GET")
	r.HandleFunc("/{gistID:[0-9]+}/{package:[a-zA-Z0-9]+}", h).Methods("GET")
	r.HandleFunc("/{gistID:[0-9]+}", h).Methods("GET")
	r.Handle("/", http.RedirectHandler("https://github.com/ImJasonH/go-gist", http.StatusSeeOther))
	http.Handle("/", r)
}
<|endoftext|>