<commit_before>package notifier

import (
	"context"
	"errors"
	"fmt"
	"net/url"
	"time"

	apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
	"github.com/prometheus/alertmanager/notify"
	"github.com/prometheus/alertmanager/types"
	"github.com/prometheus/common/model"
	"golang.org/x/sync/errgroup"
)

const (
	maxTestReceiversWorkers = 10
)

var (
	ErrNoReceivers = errors.New("no receivers")
)

type TestReceiversResult struct {
	Receivers []TestReceiverResult
	NotifedAt time.Time
}

type TestReceiverResult struct {
	Name    string
	Configs []TestReceiverConfigResult
}

type TestReceiverConfigResult struct {
	Name   string
	UID    string
	Status string
	Error  error
}

type InvalidReceiverError struct {
	Receiver *apimodels.PostableGrafanaReceiver
	Err      error
}

func (e InvalidReceiverError) Error() string {
	return fmt.Sprintf("the receiver is invalid: %s", e.Err)
}

type ReceiverTimeoutError struct {
	Receiver *apimodels.PostableGrafanaReceiver
	Err      error
}

func (e ReceiverTimeoutError) Error() string {
	return fmt.Sprintf("the receiver timed out: %s", e.Err)
}

func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestReceiversConfigParams) (*TestReceiversResult, error) {
	// now represents the start time of the test
	now := time.Now()
	testAlert := &types.Alert{
		Alert: model.Alert{
			Labels: model.LabelSet{
				model.LabelName("alertname"): "TestAlert",
				model.LabelName("instance"):  "Grafana",
			},
			Annotations: model.LabelSet{
				model.LabelName("summary"): "Notification test",
			},
			StartsAt: now,
		},
		UpdatedAt: now,
	}

	// we must set a group key that is unique per test as some receivers use this key to deduplicate alerts
	ctx = notify.WithGroupKey(ctx, testAlert.Labels.String()+now.String())

	tmpl, err := am.getTemplate()
	if err != nil {
		return nil, fmt.Errorf("failed to get template: %w", err)
	}

	// job contains all metadata required to test a receiver
	type job struct {
		Config       *apimodels.PostableGrafanaReceiver
		ReceiverName string
		Notifier     notify.Notifier
	}

	// result contains the receiver that was tested and an error that is non-nil if the test failed
	type result struct {
		Config       *apimodels.PostableGrafanaReceiver
		ReceiverName string
		Error        error
	}

	newTestReceiversResult := func(results []result, notifiedAt time.Time) *TestReceiversResult {
		m := make(map[string]TestReceiverResult)
		for _, receiver := range c.Receivers {
			// set up the result for this receiver
			m[receiver.Name] = TestReceiverResult{
				Name: receiver.Name,
				// A Grafana receiver can have multiple nested receivers
				Configs: make([]TestReceiverConfigResult, 0, len(receiver.GrafanaManagedReceivers)),
			}
		}
		for _, next := range results {
			tmp := m[next.ReceiverName]
			status := "ok"
			if next.Error != nil {
				status = "failed"
			}
			tmp.Configs = append(tmp.Configs, TestReceiverConfigResult{
				Name:   next.Config.Name,
				UID:    next.Config.UID,
				Status: status,
				Error:  processNotifierError(next.Config, next.Error),
			})
			m[next.ReceiverName] = tmp
		}
		v := new(TestReceiversResult)
		v.Receivers = make([]TestReceiverResult, 0, len(c.Receivers))
		v.NotifedAt = notifiedAt
		for _, next := range m {
			v.Receivers = append(v.Receivers, next)
		}
		return v
	}

	// invalid keeps track of all invalid receiver configurations
	invalid := make([]result, 0, len(c.Receivers))
	// jobs keeps track of all receivers that need to be sent test notifications
	jobs := make([]job, 0, len(c.Receivers))

	for _, receiver := range c.Receivers {
		for _, next := range receiver.GrafanaManagedReceivers {
			n, err := am.buildReceiverIntegration(next, tmpl)
			if err != nil {
				invalid = append(invalid, result{
					Config:       next,
					ReceiverName: next.Name,
					Error:        err,
				})
			} else {
				jobs = append(jobs, job{
					Config:       next,
					ReceiverName: receiver.Name,
					Notifier:     n,
				})
			}
		}
	}

	if len(invalid)+len(jobs) == 0 {
		return nil, ErrNoReceivers
	}

	if len(jobs) == 0 {
		return newTestReceiversResult(invalid, now), nil
	}

	numWorkers := maxTestReceiversWorkers
	if numWorkers > len(jobs) {
		numWorkers = len(jobs)
	}

	resultCh := make(chan result, len(jobs))
	workCh := make(chan job, len(jobs))
	for _, job := range jobs {
		workCh <- job
	}
	close(workCh)

	g, ctx := errgroup.WithContext(ctx)
	for i := 0; i < numWorkers; i++ {
		g.Go(func() error {
			for next := range workCh {
				v := result{
					Config:       next.Config,
					ReceiverName: next.ReceiverName,
				}
				if _, err := next.Notifier.Notify(ctx, testAlert); err != nil {
					v.Error = err
				}
				resultCh <- v
			}
			return nil
		})
	}
	g.Wait() // nolint
	close(resultCh)

	results := make([]result, 0, len(jobs))
	for next := range resultCh {
		results = append(results, next)
	}

	return newTestReceiversResult(append(invalid, results...), now), nil
}

func processNotifierError(config *apimodels.PostableGrafanaReceiver, err error) error {
	if err == nil {
		return nil
	}

	var urlError *url.Error
	if errors.As(err, &urlError) {
		if urlError.Timeout() {
			return ReceiverTimeoutError{
				Receiver: config,
				Err:      err,
			}
		}
	}

	if errors.Is(err, context.DeadlineExceeded) {
		return ReceiverTimeoutError{
			Receiver: config,
			Err:      err,
		}
	}

	return err
}
<commit_msg>Alerting: Fix flake on test receiver tests (#38511)<commit_after>package notifier

import (
	"context"
	"errors"
	"fmt"
	"net/url"
	"sort"
	"time"

	apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
	"github.com/prometheus/alertmanager/notify"
	"github.com/prometheus/alertmanager/types"
	"github.com/prometheus/common/model"
	"golang.org/x/sync/errgroup"
)

const (
	maxTestReceiversWorkers = 10
)

var (
	ErrNoReceivers = errors.New("no receivers")
)

type TestReceiversResult struct {
	Receivers []TestReceiverResult
	NotifedAt time.Time
}

type TestReceiverResult struct {
	Name    string
	Configs []TestReceiverConfigResult
}

type TestReceiverConfigResult struct {
	Name   string
	UID    string
	Status string
	Error  error
}

type InvalidReceiverError struct {
	Receiver *apimodels.PostableGrafanaReceiver
	Err      error
}

func (e InvalidReceiverError) Error() string {
	return fmt.Sprintf("the receiver is invalid: %s", e.Err)
}

type ReceiverTimeoutError struct {
	Receiver *apimodels.PostableGrafanaReceiver
	Err      error
}

func (e ReceiverTimeoutError) Error() string {
	return fmt.Sprintf("the receiver timed out: %s", e.Err)
}

func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestReceiversConfigParams) (*TestReceiversResult, error) {
	// now represents the start time of the test
	now := time.Now()
	testAlert := &types.Alert{
		Alert: model.Alert{
			Labels: model.LabelSet{
				model.LabelName("alertname"): "TestAlert",
				model.LabelName("instance"):  "Grafana",
			},
			Annotations: model.LabelSet{
				model.LabelName("summary"): "Notification test",
			},
			StartsAt: now,
		},
		UpdatedAt: now,
	}

	// we must set a group key that is unique per test as some receivers use this key to deduplicate alerts
	ctx = notify.WithGroupKey(ctx, testAlert.Labels.String()+now.String())

	tmpl, err := am.getTemplate()
	if err != nil {
		return nil, fmt.Errorf("failed to get template: %w", err)
	}

	// job contains all metadata required to test a receiver
	type job struct {
		Config       *apimodels.PostableGrafanaReceiver
		ReceiverName string
		Notifier     notify.Notifier
	}

	// result contains the receiver that was tested and an error that is non-nil if the test failed
	type result struct {
		Config       *apimodels.PostableGrafanaReceiver
		ReceiverName string
		Error        error
	}

	newTestReceiversResult := func(results []result, notifiedAt time.Time) *TestReceiversResult {
		m := make(map[string]TestReceiverResult)
		for _, receiver := range c.Receivers {
			// set up the result for this receiver
			m[receiver.Name] = TestReceiverResult{
				Name: receiver.Name,
				// A Grafana receiver can have multiple nested receivers
				Configs: make([]TestReceiverConfigResult, 0, len(receiver.GrafanaManagedReceivers)),
			}
		}
		for _, next := range results {
			tmp := m[next.ReceiverName]
			status := "ok"
			if next.Error != nil {
				status = "failed"
			}
			tmp.Configs = append(tmp.Configs, TestReceiverConfigResult{
				Name:   next.Config.Name,
				UID:    next.Config.UID,
				Status: status,
				Error:  processNotifierError(next.Config, next.Error),
			})
			m[next.ReceiverName] = tmp
		}
		v := new(TestReceiversResult)
		v.Receivers = make([]TestReceiverResult, 0, len(c.Receivers))
		v.NotifedAt = notifiedAt
		for _, next := range m {
			v.Receivers = append(v.Receivers, next)
		}

		// Make sure the return order is deterministic.
		sort.Slice(v.Receivers, func(i, j int) bool {
			return v.Receivers[i].Name < v.Receivers[j].Name
		})

		return v
	}

	// invalid keeps track of all invalid receiver configurations
	invalid := make([]result, 0, len(c.Receivers))
	// jobs keeps track of all receivers that need to be sent test notifications
	jobs := make([]job, 0, len(c.Receivers))

	for _, receiver := range c.Receivers {
		for _, next := range receiver.GrafanaManagedReceivers {
			n, err := am.buildReceiverIntegration(next, tmpl)
			if err != nil {
				invalid = append(invalid, result{
					Config:       next,
					ReceiverName: next.Name,
					Error:        err,
				})
			} else {
				jobs = append(jobs, job{
					Config:       next,
					ReceiverName: receiver.Name,
					Notifier:     n,
				})
			}
		}
	}

	if len(invalid)+len(jobs) == 0 {
		return nil, ErrNoReceivers
	}

	if len(jobs) == 0 {
		return newTestReceiversResult(invalid, now), nil
	}

	numWorkers := maxTestReceiversWorkers
	if numWorkers > len(jobs) {
		numWorkers = len(jobs)
	}

	resultCh := make(chan result, len(jobs))
	workCh := make(chan job, len(jobs))
	for _, job := range jobs {
		workCh <- job
	}
	close(workCh)

	g, ctx := errgroup.WithContext(ctx)
	for i := 0; i < numWorkers; i++ {
		g.Go(func() error {
			for next := range workCh {
				v := result{
					Config:       next.Config,
					ReceiverName: next.ReceiverName,
				}
				if _, err := next.Notifier.Notify(ctx, testAlert); err != nil {
					v.Error = err
				}
				resultCh <- v
			}
			return nil
		})
	}
	g.Wait() // nolint
	close(resultCh)

	results := make([]result, 0, len(jobs))
	for next := range resultCh {
		results = append(results, next)
	}

	return newTestReceiversResult(append(invalid, results...), now), nil
}

func processNotifierError(config *apimodels.PostableGrafanaReceiver, err error) error {
	if err == nil {
		return nil
	}

	var urlError *url.Error
	if errors.As(err, &urlError) {
		if urlError.Timeout() {
			return ReceiverTimeoutError{
				Receiver: config,
				Err:      err,
			}
		}
	}

	if errors.Is(err, context.DeadlineExceeded) {
		return ReceiverTimeoutError{
			Receiver: config,
			Err:      err,
		}
	}

	return err
}
<|endoftext|>


<commit_before>package api

import (
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/mundipagg/boleto-api/models"
)

var validate = map[string]int{
	"MP400":                   http.StatusBadRequest,
	"MPAmountInCents":         http.StatusBadRequest,
	"MPExpireDate":            http.StatusBadRequest,
	"MPBuyerDocumentType":     http.StatusBadRequest,
	"MPDocumentNumber":        http.StatusBadRequest,
	"MPRecipientDocumentType": http.StatusBadRequest,
	"MPTimeout":               http.StatusGatewayTimeout,
	"MPOurNumberFail":         http.StatusBadGateway,
}

//Esse mapper poderá ser movido para interface IBank posteriomente
var stone = map[string]int{
	"srn:error:validation":          http.StatusBadRequest,
	"srn:error:unauthenticated":     http.StatusInternalServerError,
	"srn:error:unauthorized":        http.StatusBadGateway,
	"srn:error:not_found":           http.StatusBadGateway,
	"srn:error:conflict":            http.StatusBadGateway,
	"srn:error:product_not_enabled": http.StatusBadRequest,
}

func handleErrors(c *gin.Context) {
	c.Next()

	var status int
	var exist bool

	response := getResponseFromContext(c)

	if !qualifiedForNewErrorHandling(c, response) {
		return
	}

	bank := getBankFromContext(c).GetBankNumber()
	bankcode := response.Errors[0].Code

	if status, exist = validate[bankcode]; !exist {
		status, exist = getMapper(bank)[bankcode]
	}

	switch status {
	case http.StatusBadRequest:
		response.Errors[0].Code = "MP400"
		c.JSON(http.StatusBadRequest, response)
	case http.StatusBadGateway:
		response.Errors[0].Code = "MP502"
		c.JSON(http.StatusBadGateway, response)
	case http.StatusGatewayTimeout:
		response.Errors[0].Code = "MP504"
		c.JSON(http.StatusGatewayTimeout, response)
	default:
		response.Errors[0].Code = "MP500"
		c.JSON(http.StatusInternalServerError, response)
	}

	c.Set(responseKey, response)
}

func getMapper(bank models.BankNumber) map[string]int {
	switch bank {
	case models.Stone:
		return stone
	default:
		return make(map[string]int)
	}
}

func qualifiedForNewErrorHandling(c *gin.Context, response models.BoletoResponse) bool {
	bankNumber := getBankFromContext(c).GetBankNumber()
	if bankNumber == models.Stone && response.HasErrors() {
		return true
	}
	return false
}

func getErrorCodeToLog(c *gin.Context) string {
	response := getResponseFromContext(c)
	if response.HasErrors() {
		return response.Errors[0].ErrorCode()
	}
	return ""
}
<commit_msg>:recycle: remove variável desnecessária<commit_after>package api

import (
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/mundipagg/boleto-api/models"
)

var validate = map[string]int{
	"MP400":                   http.StatusBadRequest,
	"MPAmountInCents":         http.StatusBadRequest,
	"MPExpireDate":            http.StatusBadRequest,
	"MPBuyerDocumentType":     http.StatusBadRequest,
	"MPDocumentNumber":        http.StatusBadRequest,
	"MPRecipientDocumentType": http.StatusBadRequest,
	"MPTimeout":               http.StatusGatewayTimeout,
	"MPOurNumberFail":         http.StatusBadGateway,
}

//Esse mapper poderá ser movido para interface IBank posteriomente
var stone = map[string]int{
	"srn:error:validation":          http.StatusBadRequest,
	"srn:error:unauthenticated":     http.StatusInternalServerError,
	"srn:error:unauthorized":        http.StatusBadGateway,
	"srn:error:not_found":           http.StatusBadGateway,
	"srn:error:conflict":            http.StatusBadGateway,
	"srn:error:product_not_enabled": http.StatusBadRequest,
}

func handleErrors(c *gin.Context) {
	c.Next()

	var status int
	var exist bool

	response := getResponseFromContext(c)

	if !qualifiedForNewErrorHandling(c, response) {
		return
	}

	bank := getBankFromContext(c).GetBankNumber()
	bankcode := response.Errors[0].Code

	if status, exist = validate[bankcode]; !exist {
		status, _ = getMapper(bank)[bankcode]
	}

	switch status {
	case http.StatusBadRequest:
		response.Errors[0].Code = "MP400"
		c.JSON(http.StatusBadRequest, response)
	case http.StatusBadGateway:
		response.Errors[0].Code = "MP502"
		c.JSON(http.StatusBadGateway, response)
	case http.StatusGatewayTimeout:
		response.Errors[0].Code = "MP504"
		c.JSON(http.StatusGatewayTimeout, response)
	default:
		response.Errors[0].Code = "MP500"
		c.JSON(http.StatusInternalServerError, response)
	}

	c.Set(responseKey, response)
}

func getMapper(bank models.BankNumber) map[string]int {
	switch bank {
	case models.Stone:
		return stone
	default:
		return make(map[string]int)
	}
}

func qualifiedForNewErrorHandling(c *gin.Context, response models.BoletoResponse) bool {
	bankNumber := getBankFromContext(c).GetBankNumber()
	if bankNumber == models.Stone && response.HasErrors() {
		return true
	}
	return false
}

func getErrorCodeToLog(c *gin.Context) string {
	response := getResponseFromContext(c)
	if response.HasErrors() {
		return response.Errors[0].ErrorCode()
	}
	return ""
}
<|endoftext|>


<commit_before>package joule

import (
	"errors"
	"os"
	"strconv"

	"github.com/hybridgroup/gobot"
	"github.com/hybridgroup/gobot/sysfs"
)

func writeFile(path string, data []byte) (i int, err error) {
	file, err := sysfs.OpenFile(path, os.O_WRONLY, 0644)
	defer file.Close()
	if err != nil {
		return
	}

	return file.Write(data)
}

func readFile(path string) ([]byte, error) {
	file, err := sysfs.OpenFile(path, os.O_RDONLY, 0644)
	defer file.Close()
	if err != nil {
		return make([]byte, 0), err
	}

	buf := make([]byte, 200)
	var i = 0
	i, err = file.Read(buf)
	if i == 0 {
		return buf, err
	}
	return buf[:i], err
}

type sysfsPin struct {
	pin    int
	pwmPin int
}

// JouleAdaptor represents an Intel Joule
type JouleAdaptor struct {
	name        string
	digitalPins map[int]sysfs.DigitalPin
	pwmPins     map[int]*pwmPin
	i2cDevice   sysfs.I2cDevice
	connect     func(e *JouleAdaptor) (err error)
}

var sysfsPinMap = map[string]sysfsPin{
	// disabled
	"0": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	"1": sysfsPin{
		pin:    446,
		pwmPin: -1,
	},
	"2": sysfsPin{
		pin:    421,
		pwmPin: -1,
	},
	// disabled
	"3": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	"4": sysfsPin{
		pin:    422,
		pwmPin: -1,
	},
	"5": sysfsPin{
		pin:    356,
		pwmPin: -1,
	},
	"6": sysfsPin{
		pin:    417,
		pwmPin: -1,
	},
	// UART
	"7": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	"8": sysfsPin{
		pin:    419,
		pwmPin: -1,
	},
	// disabled
	"9": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	"10": sysfsPin{
		pin:    416,
		pwmPin: -1,
	},
	"11": sysfsPin{
		pin:    381,
		pwmPin: -1,
	},
	"13": sysfsPin{
		pin:    382,
		pwmPin: -1,
	},
	"15": sysfsPin{
		pin:    380,
		pwmPin: -1,
	},
	"17": sysfsPin{
		pin:    379,
		pwmPin: -1,
	},
	"19": sysfsPin{
		pin:    378,
		pwmPin: -1,
	},
	// UART
	"21": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// disabled
	"22": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// UART
	"23": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// disabled
	"24": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	"25": sysfsPin{
		pin:    463,
		pwmPin: 0,
	},
	// low voltage should not use
	"26": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	"27": sysfsPin{
		pin:    464,
		pwmPin: 1,
	},
	// disabled
	"28": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	"29": sysfsPin{
		pin:    465,
		pwmPin: 2,
	},
	// disabled?
	"30": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	"31": sysfsPin{
		pin:    466,
		pwmPin: 3,
	},
	// disabled?
	"32": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// disabled
	"33": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	"34": sysfsPin{
		pin:    393,
		pwmPin: -1,
	},
	// GND
	"35": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// GND
	"36": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// GND
	"37": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// GND
	"38": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// disabled
	"39": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// GND
	"40": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},

	// TODO: Second header

	// LED100
	"100": sysfsPin{
		pin:    337,
		pwmPin: -1,
	},
	// LED101
	"101": sysfsPin{
		pin:    338,
		pwmPin: -1,
	},
	// LED102
	"102": sysfsPin{
		pin:    339,
		pwmPin: -1,
	},
	// LED103
	"103": sysfsPin{
		pin:    340,
		pwmPin: -1,
	},
	// LEDWIFI
	"104": sysfsPin{
		pin:    438,
		pwmPin: -1,
	},
	// LEDBT
	"105": sysfsPin{
		pin:    439,
		pwmPin: -1,
	},
}

// NewJouleAdaptor returns a new JouleAdaptor with specified name
func NewJouleAdaptor(name string) *JouleAdaptor {
	return &JouleAdaptor{
		name: name,
		connect: func(e *JouleAdaptor) (err error) {
			return
		},
	}
}

// Name returns the JouleAdaptors name
func (e *JouleAdaptor) Name() string { return e.name }

// Connect initializes the Joule for use with the Arduino beakout board
func (e *JouleAdaptor) Connect() (errs []error) {
	e.digitalPins = make(map[int]sysfs.DigitalPin)
	e.pwmPins = make(map[int]*pwmPin)
	if err := e.connect(e); err != nil {
		return []error{err}
	}
	return
}

// Finalize releases all i2c devices and exported digital and pwm pins.
func (e *JouleAdaptor) Finalize() (errs []error) {
	for _, pin := range e.digitalPins {
		if pin != nil {
			if err := pin.Unexport(); err != nil {
				errs = append(errs, err)
			}
		}
	}
	for _, pin := range e.pwmPins {
		if pin != nil {
			if err := pin.enable("0"); err != nil {
				errs = append(errs, err)
			}
			if err := pin.unexport(); err != nil {
				errs = append(errs, err)
			}
		}
	}
	if e.i2cDevice != nil {
		if err := e.i2cDevice.Close(); errs != nil {
			errs = append(errs, err)
		}
	}
	return errs
}

// digitalPin returns matched digitalPin for specified values
func (e *JouleAdaptor) digitalPin(pin string, dir string) (sysfsPin sysfs.DigitalPin, err error) {
	i := sysfsPinMap[pin]
	if e.digitalPins[i.pin] == nil {
		e.digitalPins[i.pin] = sysfs.NewDigitalPin(i.pin)
		if err = e.digitalPins[i.pin].Export(); err != nil {
			// TODO: log error
			return
		}
	}

	if dir == "in" {
		if err = e.digitalPins[i.pin].Direction(sysfs.IN); err != nil {
			return
		}
	} else if dir == "out" {
		if err = e.digitalPins[i.pin].Direction(sysfs.OUT); err != nil {
			return
		}
	}
	return e.digitalPins[i.pin], nil
}

// DigitalRead reads digital value from pin
func (e *JouleAdaptor) DigitalRead(pin string) (i int, err error) {
	sysfsPin, err := e.digitalPin(pin, "in")
	if err != nil {
		return
	}
	return sysfsPin.Read()
}

// DigitalWrite writes a value to the pin. Acceptable values are 1 or 0.
func (e *JouleAdaptor) DigitalWrite(pin string, val byte) (err error) {
	sysfsPin, err := e.digitalPin(pin, "out")
	if err != nil {
		return
	}
	return sysfsPin.Write(int(val))
}

// PwmWrite writes the 0-254 value to the specified pin
func (e *JouleAdaptor) PwmWrite(pin string, val byte) (err error) {
	sysPin := sysfsPinMap[pin]
	if sysPin.pwmPin != -1 {
		if e.pwmPins[sysPin.pwmPin] == nil {
			if err = e.DigitalWrite(pin, 1); err != nil {
				return
			}
			e.pwmPins[sysPin.pwmPin] = newPwmPin(sysPin.pwmPin)
			if err = e.pwmPins[sysPin.pwmPin].export(); err != nil {
				return
			}
			if err = e.pwmPins[sysPin.pwmPin].enable("1"); err != nil {
				return
			}
		}
		p, err := e.pwmPins[sysPin.pwmPin].period()
		if err != nil {
			return err
		}
		period, err := strconv.Atoi(p)
		if err != nil {
			return err
		}
		duty := gobot.FromScale(float64(val), 0, 255.0)
		return e.pwmPins[sysPin.pwmPin].writeDuty(strconv.Itoa(int(float64(period) * duty)))
	}
	return errors.New("Not a PWM pin")
}

// I2cStart initializes i2c device for addresss
func (e *JouleAdaptor) I2cStart(address int) (err error) {
	if e.i2cDevice != nil {
		return
	}

	// TODO: handle the additional I2C buses
	e.i2cDevice, err = sysfs.NewI2cDevice("/dev/i2c-0", address)
	return
}

// I2cWrite writes data to i2c device
func (e *JouleAdaptor) I2cWrite(address int, data []byte) (err error) {
	if err = e.i2cDevice.SetAddress(address); err != nil {
		return err
	}
	_, err = e.i2cDevice.Write(data)
	return
}

// I2cRead returns size bytes from the i2c device
func (e *JouleAdaptor) I2cRead(address int, size int) (data []byte, err error) {
	data = make([]byte, size)
	if err = e.i2cDevice.SetAddress(address); err != nil {
		return
	}
	_, err = e.i2cDevice.Read(data)
	return
}
<commit_msg>joule: adds pin mappings from the second header<commit_after>package joule

import (
	"errors"
	"os"
	"strconv"

	"github.com/hybridgroup/gobot"
	"github.com/hybridgroup/gobot/sysfs"
)

func writeFile(path string, data []byte) (i int, err error) {
	file, err := sysfs.OpenFile(path, os.O_WRONLY, 0644)
	defer file.Close()
	if err != nil {
		return
	}

	return file.Write(data)
}

func readFile(path string) ([]byte, error) {
	file, err := sysfs.OpenFile(path, os.O_RDONLY, 0644)
	defer file.Close()
	if err != nil {
		return make([]byte, 0), err
	}

	buf := make([]byte, 200)
	var i = 0
	i, err = file.Read(buf)
	if i == 0 {
		return buf, err
	}
	return buf[:i], err
}

type sysfsPin struct {
	pin    int
	pwmPin int
}

// JouleAdaptor represents an Intel Joule
type JouleAdaptor struct {
	name        string
	digitalPins map[int]sysfs.DigitalPin
	pwmPins     map[int]*pwmPin
	i2cDevice   sysfs.I2cDevice
	connect     func(e *JouleAdaptor) (err error)
}

var sysfsPinMap = map[string]sysfsPin{
	// disabled
	"0": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	"1": sysfsPin{
		pin:    446,
		pwmPin: -1,
	},
	"2": sysfsPin{
		pin:    421,
		pwmPin: -1,
	},
	// disabled
	"3": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	"4": sysfsPin{
		pin:    422,
		pwmPin: -1,
	},
	"5": sysfsPin{
		pin:    356,
		pwmPin: -1,
	},
	"6": sysfsPin{
		pin:    417,
		pwmPin: -1,
	},
	// UART
	"7": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	"8": sysfsPin{
		pin:    419,
		pwmPin: -1,
	},
	// disabled
	"9": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	"10": sysfsPin{
		pin:    416,
		pwmPin: -1,
	},
	"11": sysfsPin{
		pin:    381,
		pwmPin: -1,
	},
	"13": sysfsPin{
		pin:    382,
		pwmPin: -1,
	},
	"15": sysfsPin{
		pin:    380,
		pwmPin: -1,
	},
	"17": sysfsPin{
		pin:    379,
		pwmPin: -1,
	},
	"19": sysfsPin{
		pin:    378,
		pwmPin: -1,
	},
	// UART
	"21": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// disabled
	"22": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// UART
	"23": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// disabled
	"24": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	"25": sysfsPin{
		pin:    463,
		pwmPin: 0,
	},
	// low voltage should not use
	"26": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	"27": sysfsPin{
		pin:    464,
		pwmPin: 1,
	},
	// disabled
	"28": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	"29": sysfsPin{
		pin:    465,
		pwmPin: 2,
	},
	// disabled?
	"30": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	"31": sysfsPin{
		pin:    466,
		pwmPin: 3,
	},
	// disabled?
	"32": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// disabled
	"33": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	"34": sysfsPin{
		pin:    393,
		pwmPin: -1,
	},
	// GND
	"35": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// GND
	"36": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// GND
	"37": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// GND
	"38": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// disabled
	"39": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// GND
	"40": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},

	// Second header
	// GND
	"41": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// 5V
	"42": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// GND
	"43": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// 5V
	"44": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// GND
	"45": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// 3.3V
	"46": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// GND
	"47": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// 3.3V
	"48": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// GND
	"49": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// 1.8V
	"50": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// GPIO
	"51": sysfsPin{
		pin:    456,
		pwmPin: -1,
	},
	// 1.8V
	"52": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// PANEL
	"53": sysfsPin{
		pin:    270,
		pwmPin: -1,
	},
	// GND
	"54": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// PANEL
	"55": sysfsPin{
		pin:    271,
		pwmPin: -1,
	},
	// CAMERA
	"56": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// PANEL
	"57": sysfsPin{
		pin:    272,
		pwmPin: -1,
	},
	// CAMERA
	"58": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// SPP0FS0
	"59": sysfsPin{
		pin:    411,
		pwmPin: -1,
	},
	// CAMERA
	"60": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// SPP0FS1
	"61": sysfsPin{
		pin:    412,
		pwmPin: -1,
	},
	// SPI_DAT
	"62": sysfsPin{
		pin:    -1,
		pwmPin: -1,
	},
	// SPP0FS2
	"63": sysfsPin{
		pin:    411,
		pwmPin: -1,
	},
	// SPICLKB
	"64": sysfsPin{
		pin:    384,
		pwmPin: -1,
	},
	// SPP0FS3
	"65": sysfsPin{
		pin:    410,
		pwmPin: -1,
	},
	// SPICLKA
	"66": sysfsPin{
		pin:    383,
		pwmPin: -1,
	},
	// SPP0TX
	"67": sysfsPin{
		pin:    414,
		pwmPin: -1,
	},
	// UART0RX
	"68": sysfsPin{
		pin:    467,
		pwmPin: -1,
	},
	// SPP0RX
	"69": sysfsPin{
		pin:    415,
		pwmPin: -1,
	},
	// UART0RT
	"70": sysfsPin{
		pin:    469,
		pwmPin: -1,
	},
	// I2C1SDA
	"71": sysfsPin{
		pin:    317,
		pwmPin: -1,
	},
	// UART0CT
	"72": sysfsPin{
		pin:    412,
		pwmPin: -1,
	},
	// I2C1SCL
	"73": sysfsPin{
		pin:    418,
		pwmPin: -1,
	},
	// UART1TX
	"74": sysfsPin{
		pin:    484,
		pwmPin: -1,
	},
	// I2C2SDA
	"75": sysfsPin{
		pin:    319,
		pwmPin: -1,
	},
	// UART1RX
	"76": sysfsPin{
		pin:    483,
		pwmPin: -1,
	},
	// I2C2SCL
	"77": sysfsPin{
		pin:    320,
		pwmPin: -1,
	},
	// UART1RT
	"78": sysfsPin{
		pin:    485,
		pwmPin: -1,
	},
	// RTC_CLK
	"79": sysfsPin{
		pin:    367,
		pwmPin: -1,
	},
	// UART1CT
	"80": sysfsPin{
		pin:    486,
		pwmPin: -1,
	},

	// Built-in LEDs
	// LED100
	"100": sysfsPin{
		pin:    337,
		pwmPin: -1,
	},
	// LED101
	"101": sysfsPin{
		pin:    338,
		pwmPin: -1,
	},
	// LED102
	"102": sysfsPin{
		pin:    339,
		pwmPin: -1,
	},
	// LED103
	"103": sysfsPin{
		pin:    340,
		pwmPin: -1,
	},
	// LEDWIFI
	"104": sysfsPin{
		pin:    438,
		pwmPin: -1,
	},
	// LEDBT
	"105": sysfsPin{
		pin:    439,
		pwmPin: -1,
	},
}

// NewJouleAdaptor returns a new JouleAdaptor with specified name
func NewJouleAdaptor(name string) *JouleAdaptor {
	return &JouleAdaptor{
		name: name,
		connect: func(e *JouleAdaptor) (err error) {
			return
		},
	}
}

// Name returns the JouleAdaptors name
func (e *JouleAdaptor) Name() string { return e.name }

// Connect initializes the Joule for use with the Arduino beakout board
func (e *JouleAdaptor) Connect() (errs []error) {
	e.digitalPins = make(map[int]sysfs.DigitalPin)
	e.pwmPins = make(map[int]*pwmPin)
	if err := e.connect(e); err != nil {
		return []error{err}
	}
	return
}

// Finalize releases all i2c devices and exported digital and pwm pins.
func (e *JouleAdaptor) Finalize() (errs []error) {
	for _, pin := range e.digitalPins {
		if pin != nil {
			if err := pin.Unexport(); err != nil {
				errs = append(errs, err)
			}
		}
	}
	for _, pin := range e.pwmPins {
		if pin != nil {
			if err := pin.enable("0"); err != nil {
				errs = append(errs, err)
			}
			if err := pin.unexport(); err != nil {
				errs = append(errs, err)
			}
		}
	}
	if e.i2cDevice != nil {
		if err := e.i2cDevice.Close(); errs != nil {
			errs = append(errs, err)
		}
	}
	return errs
}

// digitalPin returns matched digitalPin for specified values
func (e *JouleAdaptor) digitalPin(pin string, dir string) (sysfsPin sysfs.DigitalPin, err error) {
	i := sysfsPinMap[pin]
	if e.digitalPins[i.pin] == nil {
		e.digitalPins[i.pin] = sysfs.NewDigitalPin(i.pin)
		if err = e.digitalPins[i.pin].Export(); err != nil {
			// TODO: log error
			return
		}
	}

	if dir == "in" {
		if err = e.digitalPins[i.pin].Direction(sysfs.IN); err != nil {
			return
		}
	} else if dir == "out" {
		if err = e.digitalPins[i.pin].Direction(sysfs.OUT); err != nil {
			return
		}
	}
	return e.digitalPins[i.pin], nil
}

// DigitalRead reads digital value from pin
func (e *JouleAdaptor) DigitalRead(pin string) (i int, err error) {
	sysfsPin, err := e.digitalPin(pin, "in")
	if err != nil {
		return
	}
	return sysfsPin.Read()
}

// DigitalWrite writes a value to the pin. Acceptable values are 1 or 0.
func (e *JouleAdaptor) DigitalWrite(pin string, val byte) (err error) {
	sysfsPin, err := e.digitalPin(pin, "out")
	if err != nil {
		return
	}
	return sysfsPin.Write(int(val))
}

// PwmWrite writes the 0-254 value to the specified pin
func (e *JouleAdaptor) PwmWrite(pin string, val byte) (err error) {
	sysPin := sysfsPinMap[pin]
	if sysPin.pwmPin != -1 {
		if e.pwmPins[sysPin.pwmPin] == nil {
			if err = e.DigitalWrite(pin, 1); err != nil {
				return
			}
			e.pwmPins[sysPin.pwmPin] = newPwmPin(sysPin.pwmPin)
			if err = e.pwmPins[sysPin.pwmPin].export(); err != nil {
				return
			}
			if err = e.pwmPins[sysPin.pwmPin].enable("1"); err != nil {
				return
			}
		}
		p, err := e.pwmPins[sysPin.pwmPin].period()
		if err != nil {
			return err
		}
		period, err := strconv.Atoi(p)
		if err != nil {
			return err
		}
		duty := gobot.FromScale(float64(val), 0, 255.0)
		return e.pwmPins[sysPin.pwmPin].writeDuty(strconv.Itoa(int(float64(period) * duty)))
	}
	return errors.New("Not a PWM pin")
}

// I2cStart initializes i2c device for addresss
func (e *JouleAdaptor) I2cStart(address int) (err error) {
	if e.i2cDevice != nil {
		return
	}

	// TODO: handle the additional I2C buses
	e.i2cDevice, err = sysfs.NewI2cDevice("/dev/i2c-0", address)
	return
}

// I2cWrite writes data to i2c device
func (e *JouleAdaptor) I2cWrite(address int, data []byte) (err error) {
	if err = e.i2cDevice.SetAddress(address); err != nil {
		return err
	}
	_, err = e.i2cDevice.Write(data)
	return
}

// I2cRead returns size bytes from the i2c device
func (e *JouleAdaptor) I2cRead(address int, size int) (data []byte, err error) {
	data = make([]byte, size)
	if err = e.i2cDevice.SetAddress(address); err != nil {
		return
	}
	_, err = e.i2cDevice.Read(data)
	return
}
<|endoftext|>


<commit_before>package v7_test

import (
	"errors"

	"code.cloudfoundry.org/cli/actor/actionerror"
	"code.cloudfoundry.org/cli/actor/v7action"
	"code.cloudfoundry.org/cli/command/commandfakes"
	"code.cloudfoundry.org/cli/command/flag"
	"code.cloudfoundry.org/cli/command/translatableerror"
	. "code.cloudfoundry.org/cli/command/v7"
	"code.cloudfoundry.org/cli/command/v7/v7fakes"
	"code.cloudfoundry.org/cli/types"
	"code.cloudfoundry.org/cli/util/configv3"
	"code.cloudfoundry.org/cli/util/ui"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
)

var _ = Describe("update-service command", func() {
	const (
		serviceInstanceName = "fake-service-instance-name"
		spaceName           = "fake-space-name"
		spaceGUID           = "fake-space-guid"
		orgName             = "fake-org-name"
		username            = "fake-username"
	)

	var (
		input           *Buffer
		testUI          *ui.UI
		fakeConfig      *commandfakes.FakeConfig
		fakeSharedActor *commandfakes.FakeSharedActor
		fakeActor       *v7fakes.FakeActor
		cmd             UpdateServiceCommand
		executeErr      error
	)

	BeforeEach(func() {
		input = NewBuffer()
		testUI = ui.NewTestUI(input, NewBuffer(), NewBuffer())
		fakeConfig = new(commandfakes.FakeConfig)
		fakeSharedActor = new(commandfakes.FakeSharedActor)
		fakeActor = new(v7fakes.FakeActor)

		cmd = UpdateServiceCommand{
			BaseCommand: BaseCommand{
				UI:          testUI,
				Config:      fakeConfig,
				SharedActor: fakeSharedActor,
				Actor:       fakeActor,
			},
		}

		setPositionalFlags(&cmd, serviceInstanceName)

		fakeConfig.TargetedOrganizationReturns(configv3.Organization{Name: orgName})
		fakeConfig.TargetedSpaceReturns(configv3.Space{
			Name: spaceName,
			GUID: spaceGUID,
		})
		fakeConfig.CurrentUserReturns(configv3.User{Name: username}, nil)

		fakeActor.UpdateManagedServiceInstanceReturns(
			v7action.Warnings{"actor warning"},
			nil,
		)
	})

	JustBeforeEach(func() {
		executeErr = cmd.Execute(nil)
	})

	It("checks the user is logged in, and targeting an org and space", func() {
		Expect(fakeSharedActor.CheckTargetCallCount()).To(Equal(1))
		orgChecked, spaceChecked := fakeSharedActor.CheckTargetArgsForCall(0)
		Expect(orgChecked).To(BeTrue())
		Expect(spaceChecked).To(BeTrue())
	})

	When("no parameters specified", func() {
		It("prints a message and exits 0", func() {
			Expect(executeErr).NotTo(HaveOccurred())
			Expect(testUI.Out).To(Say("No flags specified. No changes were made"))
		})
	})

	Describe("updates", func() {
		BeforeEach(func() {
			setFlag(&cmd, "-t", flag.Tags{IsSet: true, Value: []string{"foo", "bar"}})
			setFlag(&cmd, "-c", flag.JSONOrFileWithValidation{
				IsSet: true,
				Value: map[string]interface{}{"baz": "quz"},
			})
			setFlag(&cmd, "-p", flag.OptionalString{IsSet: true, Value: "some-plan"})
		})

		It("does not return an error", func() {
			Expect(executeErr).NotTo(HaveOccurred())
		})

		It("prints messages and warnings", func() {
			Expect(testUI.Out).To(SatisfyAll(
				Say(`Updating service instance %s in org %s / space %s as %s...\n`, serviceInstanceName, orgName, spaceName, username),
				Say(`\n`),
				Say(`OK\n`),
			))

			Expect(testUI.Err).To(Say("actor warning"))
		})

		It("delegates to the actor", func() {
			Expect(fakeActor.UpdateManagedServiceInstanceCallCount()).To(Equal(1))
			actualName, actualSpaceGUID, actualUpdates := fakeActor.UpdateManagedServiceInstanceArgsForCall(0)
			Expect(actualName).To(Equal(serviceInstanceName))
			Expect(actualSpaceGUID).To(Equal(spaceGUID))
			Expect(actualUpdates).To(Equal(v7action.ServiceInstanceUpdateManagedParams{
				Tags:            types.NewOptionalStringSlice("foo", "bar"),
				Parameters:      types.NewOptionalObject(map[string]interface{}{"baz": "quz"}),
				ServicePlanName: types.NewOptionalString("some-plan"),
			}))
		})

		When("getting the user fails", func() {
			BeforeEach(func() {
				fakeConfig.CurrentUserReturns(configv3.User{}, errors.New("bang"))
			})

			It("returns the error", func() {
				Expect(executeErr).To(MatchError("bang"))
			})
		})

		When("the actor reports the service instance was not found", func() {
			BeforeEach(func() {
				fakeActor.UpdateManagedServiceInstanceReturns(
					v7action.Warnings{"actor warning"},
					actionerror.ServiceInstanceNotFoundError{},
				)
			})

			It("prints warnings and returns a translatable error", func() {
				Expect(testUI.Err).To(Say("actor warning"))
				Expect(executeErr).To(MatchError(translatableerror.ServiceInstanceNotFoundError{
					Name: serviceInstanceName,
				}))
			})
		})

		When("plan not found", func() {
			const (
				invalidPlan = "invalid-plan"
			)

			BeforeEach(func() {
				setFlag(&cmd, "-p", flag.OptionalString{IsSet: true, Value: invalidPlan})
				fakeActor.UpdateManagedServiceInstanceReturns(
					v7action.Warnings{"actor warning"},
					actionerror.ServicePlanNotFoundError{PlanName: invalidPlan, ServiceBrokerName: "the-broker", OfferingName: "the-offering"},
				)
			})

			It("prints warnings and returns a translatable error", func() {
				Expect(testUI.Err).To(Say("actor warning"))
				Expect(executeErr).To(MatchError(actionerror.ServicePlanNotFoundError{
					PlanName:          invalidPlan,
					OfferingName:      "the-offering",
					ServiceBrokerName: "the-broker",
				}))
			})
		})

		When("the actor fails with an unexpected error", func() {
			BeforeEach(func() {
				fakeActor.UpdateManagedServiceInstanceReturns(
					v7action.Warnings{"actor warning"},
					errors.New("boof"),
				)
			})

			It("prints warnings and returns an error", func() {
				Expect(testUI.Err).To(Say("actor warning"))
				Expect(executeErr).To(MatchError("boof"))
			})
		})
	})

	When("checking the target returns an error", func() {
		BeforeEach(func() {
			fakeSharedActor.CheckTargetReturns(errors.New("explode"))
		})

		It("returns the error", func() {
			Expect(executeErr).To(MatchError("explode"))
		})
	})
})
<commit_msg>v8(tests): fix failing unit test<commit_after>package v7_test

import (
	"errors"

	"code.cloudfoundry.org/cli/actor/actionerror"
	"code.cloudfoundry.org/cli/actor/v7action"
	"code.cloudfoundry.org/cli/command/commandfakes"
	"code.cloudfoundry.org/cli/command/flag"
	. "code.cloudfoundry.org/cli/command/v7"
	"code.cloudfoundry.org/cli/command/v7/v7fakes"
	"code.cloudfoundry.org/cli/types"
	"code.cloudfoundry.org/cli/util/configv3"
	"code.cloudfoundry.org/cli/util/ui"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
)

var _ = Describe("update-service command", func() {
	const (
		serviceInstanceName = "fake-service-instance-name"
		spaceName           = "fake-space-name"
		spaceGUID           = "fake-space-guid"
		orgName             = "fake-org-name"
		username            = "fake-username"
	)

	var (
		input           *Buffer
		testUI          *ui.UI
		fakeConfig      *commandfakes.FakeConfig
		fakeSharedActor *commandfakes.FakeSharedActor
		fakeActor       *v7fakes.FakeActor
		cmd             UpdateServiceCommand
		executeErr      error
	)

	BeforeEach(func() {
		input = NewBuffer()
		testUI = ui.NewTestUI(input, NewBuffer(), NewBuffer())
		fakeConfig = new(commandfakes.FakeConfig)
		fakeSharedActor = new(commandfakes.FakeSharedActor)
		fakeActor = new(v7fakes.FakeActor)

		cmd = UpdateServiceCommand{
			BaseCommand: BaseCommand{
				UI:          testUI,
				Config:      fakeConfig,
				SharedActor: fakeSharedActor,
				Actor:       fakeActor,
			},
		}

		setPositionalFlags(&cmd, serviceInstanceName)

		fakeConfig.TargetedOrganizationReturns(configv3.Organization{Name: orgName})
		fakeConfig.TargetedSpaceReturns(configv3.Space{
			Name: spaceName,
			GUID: spaceGUID,
		})
		fakeConfig.CurrentUserReturns(configv3.User{Name: username}, nil)

		fakeActor.UpdateManagedServiceInstanceReturns(
			v7action.Warnings{"actor warning"},
			nil,
		)
	})

	JustBeforeEach(func() {
		executeErr = cmd.Execute(nil)
	})

	It("checks the user is logged in, and targeting an org and space", func() {
		Expect(fakeSharedActor.CheckTargetCallCount()).To(Equal(1))
		orgChecked, spaceChecked := fakeSharedActor.CheckTargetArgsForCall(0)
		Expect(orgChecked).To(BeTrue())
		Expect(spaceChecked).To(BeTrue())
	})

	When("no parameters specified", func() {
		It("prints a message and exits 0", func() {
			Expect(executeErr).NotTo(HaveOccurred())
			Expect(testUI.Out).To(Say("No flags specified. No changes were made"))
		})
	})

	Describe("updates", func() {
		BeforeEach(func() {
			setFlag(&cmd, "-t", flag.Tags{IsSet: true, Value: []string{"foo", "bar"}})
			setFlag(&cmd, "-c", flag.JSONOrFileWithValidation{
				IsSet: true,
				Value: map[string]interface{}{"baz": "quz"},
			})
			setFlag(&cmd, "-p", flag.OptionalString{IsSet: true, Value: "some-plan"})
		})

		It("does not return an error", func() {
			Expect(executeErr).NotTo(HaveOccurred())
		})

		It("prints messages and warnings", func() {
			Expect(testUI.Out).To(SatisfyAll(
				Say(`Updating service instance %s in org %s / space %s as %s...\n`, serviceInstanceName, orgName, spaceName, username),
				Say(`\n`),
				Say(`OK\n`),
			))

			Expect(testUI.Err).To(Say("actor warning"))
		})

		It("delegates to the actor", func() {
			Expect(fakeActor.UpdateManagedServiceInstanceCallCount()).To(Equal(1))
			actualName, actualSpaceGUID, actualUpdates := fakeActor.UpdateManagedServiceInstanceArgsForCall(0)
			Expect(actualName).To(Equal(serviceInstanceName))
			Expect(actualSpaceGUID).To(Equal(spaceGUID))
			Expect(actualUpdates).To(Equal(v7action.ServiceInstanceUpdateManagedParams{
				Tags:            types.NewOptionalStringSlice("foo", "bar"),
				Parameters:      types.NewOptionalObject(map[string]interface{}{"baz": "quz"}),
				ServicePlanName: types.NewOptionalString("some-plan"),
			}))
		})

		When("getting the user fails", func() {
			BeforeEach(func() {
				fakeConfig.CurrentUserReturns(configv3.User{}, errors.New("bang"))
			})

			It("returns the error", func() {
				Expect(executeErr).To(MatchError("bang"))
			})
		})

		When("the actor reports the service instance was not found", func() {
			BeforeEach(func() {
				fakeActor.UpdateManagedServiceInstanceReturns(
					v7action.Warnings{"actor warning"},
					actionerror.ServiceInstanceNotFoundError{Name: serviceInstanceName},
				)
			})

			It("prints warnings and returns an error", func() {
				Expect(testUI.Err).To(Say("actor warning"))
				Expect(executeErr).To(MatchError(actionerror.ServiceInstanceNotFoundError{
					Name: serviceInstanceName,
				}))
			})
		})

		When("plan not found", func() {
			const (
				invalidPlan = "invalid-plan"
			)

			BeforeEach(func() {
				setFlag(&cmd, "-p", flag.OptionalString{IsSet: true, Value: invalidPlan})
				fakeActor.UpdateManagedServiceInstanceReturns(
					v7action.Warnings{"actor warning"},
					actionerror.ServicePlanNotFoundError{PlanName: invalidPlan, ServiceBrokerName: "the-broker", OfferingName: "the-offering"},
				)
			})

			It("prints warnings and returns a translatable error", func() {
				Expect(testUI.Err).To(Say("actor warning"))
				Expect(executeErr).To(MatchError(actionerror.ServicePlanNotFoundError{
					PlanName:          invalidPlan,
					OfferingName:      "the-offering",
					ServiceBrokerName: "the-broker",
				}))
			})
		})

		When("the actor fails with an unexpected error", func() {
			BeforeEach(func() {
				fakeActor.UpdateManagedServiceInstanceReturns(
					v7action.Warnings{"actor warning"},
					errors.New("boof"),
				)
			})

			It("prints warnings and returns an error", func() {
				Expect(testUI.Err).To(Say("actor warning"))
				Expect(executeErr).To(MatchError("boof"))
			})
		})
	})

	When("checking the target returns an error", func() {
		BeforeEach(func() {
			fakeSharedActor.CheckTargetReturns(errors.New("explode"))
		})

		It("returns the error", func() {
			Expect(executeErr).To(MatchError("explode"))
		})
	})
})
<|endoftext|>


<commit_before>// Copyright 2015 The Chihaya Authors. All rights reserved.
// Use of this source code is governed by the BSD 2-Clause license,
// which can be found in the LICENSE file.

package api

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
	"net/url"
	"runtime"

	"github.com/julienschmidt/httprouter"

	"github.com/chihaya/chihaya/stats"
	"github.com/chihaya/chihaya/tracker/models"
)

const jsonContentType = "application/json; charset=UTF-8"

func handleError(err error) (int, error) {
	if err == nil {
		return http.StatusOK, nil
	} else if _, ok := err.(models.NotFoundError); ok {
		stats.RecordEvent(stats.ClientError)
		return http.StatusNotFound, nil
	} else if _, ok := err.(models.ClientError); ok {
		stats.RecordEvent(stats.ClientError)
		return http.StatusBadRequest, nil
	}
	return http.StatusInternalServerError, err
}

func (s *Server) check(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) {
	// Attempt to ping the backend if private tracker is enabled.
	if s.config.PrivateEnabled {
		if err := s.tracker.Backend.Ping(); err != nil {
			return handleError(err)
		}
	}

	_, err := w.Write([]byte("STILL-ALIVE"))
	return handleError(err)
}

func (s *Server) stats(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) {
	w.Header().Set("Content-Type", jsonContentType)

	var err error
	var val interface{}
	query := r.URL.Query()

	stats.DefaultStats.GoRoutines = runtime.NumGoroutine()

	if _, flatten := query["flatten"]; flatten {
		val = stats.DefaultStats.Flattened()
	} else {
		val = stats.DefaultStats
	}

	if _, pretty := query["pretty"]; pretty {
		var buf []byte
		buf, err = json.MarshalIndent(val, "", "  ")

		if err == nil {
			_, err = w.Write(buf)
		}
	} else {
		err = json.NewEncoder(w).Encode(val)
	}

	return handleError(err)
}

func (s *Server) getTorrent(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) {
	infohash, err := url.QueryUnescape(p.ByName("infohash"))
	if err != nil {
		return http.StatusNotFound, err
	}

	torrent, err := s.tracker.FindTorrent(infohash)
	if err != nil {
		return handleError(err)
	}

	w.Header().Set("Content-Type", jsonContentType)
	e := json.NewEncoder(w)
	return handleError(e.Encode(torrent))
}

func (s *Server) putTorrent(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return http.StatusInternalServerError, err
	}

	var torrent models.Torrent
	err = json.Unmarshal(body, &torrent)
	if err != nil {
		return http.StatusBadRequest, err
	}

	s.tracker.PutTorrent(&torrent)
	return http.StatusOK, nil
}

func (s *Server) delTorrent(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) {
	infohash, err := url.QueryUnescape(p.ByName("infohash"))
	if err != nil {
		return http.StatusNotFound, err
	}

	s.tracker.DeleteTorrent(infohash)
	return http.StatusOK, nil
}

func (s *Server) getUser(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) {
	user, err := s.tracker.FindUser(p.ByName("passkey"))
	if err == models.ErrUserDNE {
		return http.StatusNotFound, err
	} else if err != nil {
		return http.StatusInternalServerError, err
	}

	w.Header().Set("Content-Type", jsonContentType)
	e := json.NewEncoder(w)
	return handleError(e.Encode(user))
}

func (s *Server) putUser(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return http.StatusInternalServerError, err
	}

	var user models.User
	err = json.Unmarshal(body, &user)
	if err != nil {
		return http.StatusBadRequest, err
	}

	s.tracker.PutUser(&user)
	return http.StatusOK, nil
}

func (s *Server) delUser(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) {
	s.tracker.DeleteUser(p.ByName("passkey"))
	return http.StatusOK, nil
}

func (s *Server) getClient(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) {
	if err := s.tracker.ClientApproved(p.ByName("clientID")); err != nil {
		return http.StatusNotFound, err
	}
	return http.StatusOK, nil
}

func (s *Server) putClient(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) {
	s.tracker.PutClient(p.ByName("clientID"))
	return http.StatusOK, nil
}

func (s *Server) delClient(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) {
	s.tracker.DeleteClient(p.ByName("clientID"))
	return http.StatusOK, nil
}
<commit_msg>remove superfluous use of ioutil<commit_after>// Copyright 2015 The Chihaya Authors. All rights reserved.
// Use of this source code is governed by the BSD 2-Clause license,
// which can be found in the LICENSE file.

package api

import (
	"encoding/json"
	"net/http"
	"net/url"
	"runtime"

	"github.com/julienschmidt/httprouter"

	"github.com/chihaya/chihaya/stats"
	"github.com/chihaya/chihaya/tracker/models"
)

const jsonContentType = "application/json; charset=UTF-8"

func handleError(err error) (int, error) {
	if err == nil {
		return http.StatusOK, nil
	} else if _, ok := err.(models.NotFoundError); ok {
		stats.RecordEvent(stats.ClientError)
		return http.StatusNotFound, nil
	} else if _, ok := err.(models.ClientError); ok {
		stats.RecordEvent(stats.ClientError)
		return http.StatusBadRequest, nil
	}
	return http.StatusInternalServerError, err
}

func (s *Server) check(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) {
	// Attempt to ping the backend if private tracker is enabled.
	if s.config.PrivateEnabled {
		if err := s.tracker.Backend.Ping(); err != nil {
			return handleError(err)
		}
	}

	_, err := w.Write([]byte("STILL-ALIVE"))
	return handleError(err)
}

func (s *Server) stats(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) {
	w.Header().Set("Content-Type", jsonContentType)

	var err error
	var val interface{}
	query := r.URL.Query()

	stats.DefaultStats.GoRoutines = runtime.NumGoroutine()

	if _, flatten := query["flatten"]; flatten {
		val = stats.DefaultStats.Flattened()
	} else {
		val = stats.DefaultStats
	}

	if _, pretty := query["pretty"]; pretty {
		var buf []byte
		buf, err = json.MarshalIndent(val, "", "  ")

		if err == nil {
			_, err = w.Write(buf)
		}
	} else {
		err = json.NewEncoder(w).Encode(val)
	}

	return handleError(err)
}

func (s *Server) getTorrent(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) {
	infohash, err := url.QueryUnescape(p.ByName("infohash"))
	if err != nil {
		return http.StatusNotFound, err
	}

	torrent, err := s.tracker.FindTorrent(infohash)
	if err != nil {
		return handleError(err)
	}

	w.Header().Set("Content-Type", jsonContentType)
	e := json.NewEncoder(w)
	return handleError(e.Encode(torrent))
}

func (s *Server) putTorrent(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) {
	var torrent models.Torrent
	err := json.NewDecoder(r.Body).Decode(&torrent)
	if err != nil {
		return http.StatusBadRequest, err
	}

	s.tracker.PutTorrent(&torrent)
	return http.StatusOK, nil
}

func (s *Server) delTorrent(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) {
	infohash, err := url.QueryUnescape(p.ByName("infohash"))
	if err != nil {
		return http.StatusNotFound, err
	}

	s.tracker.DeleteTorrent(infohash)
	return http.StatusOK, nil
}

func (s *Server) getUser(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) {
	user, err := s.tracker.FindUser(p.ByName("passkey"))
	if err == models.ErrUserDNE {
		return http.StatusNotFound, err
	} else if err != nil {
		return http.StatusInternalServerError, err
	}

	w.Header().Set("Content-Type", jsonContentType)
	e := json.NewEncoder(w)
	return handleError(e.Encode(user))
}

func (s *Server) putUser(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) {
	var user models.User
	err := json.NewDecoder(r.Body).Decode(&user)
	if err != nil {
		return http.StatusBadRequest, err
	}

	s.tracker.PutUser(&user)
	return http.StatusOK, nil
}

func (s *Server) delUser(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) {
	s.tracker.DeleteUser(p.ByName("passkey"))
	return http.StatusOK, nil
}

func (s *Server) getClient(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) {
	if err := s.tracker.ClientApproved(p.ByName("clientID")); err != nil {
		return http.StatusNotFound, err
	}
	return http.StatusOK, nil
}

func (s *Server) putClient(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) {
	s.tracker.PutClient(p.ByName("clientID"))
	return http.StatusOK, nil
}

func (s *Server) delClient(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) {
	s.tracker.DeleteClient(p.ByName("clientID"))
	return http.StatusOK, nil
}
<|endoftext|>


<commit_before>package api

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/gempir/justlog/helix"

	twitch "github.com/gempir/go-twitch-irc"
	"github.com/gempir/justlog/filelog"
	jsoniter "github.com/json-iterator/go"
	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"

	_ "github.com/gempir/justlog/docs"
	"github.com/swaggo/echo-swagger"
)

type Server struct {
	listenAddress string
	logPath       string
	fileLogger    *filelog.Logger
	helixClient   *helix.Client
	channels      []string
}

func NewServer(logPath string, listenAddress string, fileLogger *filelog.Logger, helixClient *helix.Client) Server {
	return Server{
		listenAddress: listenAddress,
		logPath:       logPath,
		fileLogger:    fileLogger,
		helixClient:   helixClient,
		channels:      []string{},
	}
}

func (s *Server) AddChannel(channel string) {
	s.channels = append(s.channels, channel)
}

func (s *Server) Init() {

	e := echo.New()
	e.HideBanner = true

	DefaultCORSConfig := middleware.CORSConfig{
		Skipper:      middleware.DefaultSkipper,
		AllowOrigins: []string{"*"},
		AllowMethods: []string{echo.GET, echo.HEAD, echo.PUT, echo.PATCH, echo.POST, echo.DELETE},
	}
	e.Use(middleware.RemoveTrailingSlashWithConfig(middleware.TrailingSlashConfig{
		RedirectCode: http.StatusMovedPermanently,
	}))
	e.Use(middleware.CORSWithConfig(DefaultCORSConfig))

	e.GET("/", func(c echo.Context) error {
		return c.Redirect(http.StatusMovedPermanently, "/index.html")
	})
	e.GET("/*", echoSwagger.WrapHandler)
	e.GET("/channelid", s.getAllChannels)

	e.GET("/channel/:channel/user/:username/range", s.getUserLogsRangeByName)
	e.GET("/channelid/:channelid/userid/:userid/range", s.getUserLogsRange)

	e.GET("/channel/:channel/user/:username", s.getLastUserLogsByName)
	e.GET("/channel/:channel/user/:username/:year/:month", s.getUserLogsByName)
	e.GET("/channel/:channel/user/:username/random", s.getRandomQuoteByName)

	e.GET("/channelid/:channelid/user/:userid", s.getLastUserLogs)
	e.GET("/channelid/:channelid/userid/:userid/:year/:month", s.getUserLogs)
	e.GET("/channelid/:channelid/userid/:userid/random", s.getRandomQuote)

	e.GET("/channelid/:channelid/range", s.getChannelLogsRange)
	e.GET("/channel/:channel/range", s.getChannelLogsRangeByName)

	e.GET("/channel/:channel", s.getCurrentChannelLogsByName)
	e.GET("/channel/:channel/:year/:month/:day", s.getChannelLogsByName)
	e.GET("/channelid/:channelid", s.getCurrentChannelLogs)
	e.GET("/channelid/:channelid/:year/:month/:day", s.getChannelLogs)

	e.Logger.Fatal(e.Start(s.listenAddress))
}

var (
	userHourLimit    = 744.0
	channelHourLimit = 24.0
)

type AllChannelsJSON struct {
	Channels []string `json:"channels"`
}

type chatLog struct {
	Messages []chatMessage `json:"messages"`
}

type chatMessage struct {
	Text        string             `json:"text"`
	Username    string             `json:"username"`
	DisplayName string             `json:"displayName"`
	Channel     string             `json:"channel"`
	Timestamp   timestamp          `json:"timestamp"`
	Type        twitch.MessageType `json:"type"`
}

type errorResponse struct {
	Message string `json:"message"`
}

type timestamp struct {
	time.Time
}

func reverse(input []string) []string {
	for i, j := 0, len(input)-1; i < j; i, j = i+1, j-1 {
		input[i], input[j] = input[j], input[i]
	}
	return input
}

func (s *Server) getAllChannels(c echo.Context) error {
	response := new(AllChannelsJSON)
	response.Channels = s.channels

	return c.JSON(http.StatusOK, response)
}

func (t timestamp) MarshalJSON() ([]byte, error) {
	return []byte("\"" + t.UTC().Format(time.RFC3339) + "\""), nil
}

func (t *timestamp) UnmarshalJSON(data []byte) error {
	goTime, err := time.Parse(time.RFC3339, strings.TrimSuffix(strings.TrimPrefix(string(data[:]), "\""), "\""))
	if err != nil {
		return err
	}
	*t = timestamp{
		goTime,
	}
	return nil
}

func parseFromTo(from, to string, limit float64) (time.Time, time.Time, error) {
	var fromTime time.Time
	var toTime time.Time

	if from == "" && to == "" {
		fromTime = time.Now().AddDate(0, -1, 0)
		toTime = time.Now()
	} else if from == "" && to != "" {
		var err error
		toTime, err = parseTimestamp(to)
		if err != nil {
			return fromTime, toTime, fmt.Errorf("Can't parse to timestamp: %s", err)
		}
		fromTime = toTime.AddDate(0, -1, 0)
	} else if from != "" && to == "" {
		var err error
		fromTime, err = parseTimestamp(from)
		if err != nil {
			return fromTime, toTime, fmt.Errorf("Can't parse from timestamp: %s", err)
		}
		toTime = fromTime.AddDate(0, 1, 0)
	} else {
		var err error

		fromTime, err = parseTimestamp(from)
		if err != nil {
			return fromTime, toTime, fmt.Errorf("Can't parse from timestamp: %s", err)
		}
		toTime, err = parseTimestamp(to)
		if err != nil {
			return fromTime, toTime, fmt.Errorf("Can't parse to timestamp: %s", err)
		}

		if toTime.Sub(fromTime).Hours() > limit {
			return fromTime, toTime, errors.New("Timespan too big")
		}
	}

	return fromTime, toTime, nil
}

func writeTextResponse(c echo.Context, cLog *chatLog) error {
	c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextPlainCharsetUTF8)
	c.Response().WriteHeader(http.StatusOK)

	for _, cMessage := range cLog.Messages {
		switch cMessage.Type {
		case twitch.PRIVMSG:
			c.Response().Write([]byte(fmt.Sprintf("[%s] #%s %s: %s\n", cMessage.Timestamp.Format("2006-01-2 15:04:05"), cMessage.Channel, cMessage.Username, cMessage.Text)))
		case twitch.CLEARCHAT:
			c.Response().Write([]byte(fmt.Sprintf("[%s] #%s %s\n", cMessage.Timestamp.Format("2006-01-2 15:04:05"), cMessage.Channel, cMessage.Text)))
		}
	}

	return nil
}

func writeJSONResponse(c echo.Context, logResult *chatLog) error {
	_, stream := c.QueryParams()["stream"]
	if stream {
		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
		c.Response().WriteHeader(http.StatusOK)

		return json.NewEncoder(c.Response()).Encode(logResult)
	}

	json := jsoniter.ConfigCompatibleWithStandardLibrary
	data, err := json.Marshal(logResult)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, err.Error())
	}

	return c.Blob(http.StatusOK, echo.MIMEApplicationJSONCharsetUTF8, data)
}

func parseTimestamp(timestamp string) (time.Time, error) {

	i, err := strconv.ParseInt(timestamp, 10, 64)
	if err != nil {
		return time.Now(), err
	}
	return time.Unix(i, 0), nil
}

func shouldReverse(c echo.Context) bool {
	_, ok := c.QueryParams()["reverse"]

	return c.QueryParam("order") == "reverse" || ok
}

func shouldRespondWithJson(c echo.Context) bool {
	_, ok := c.QueryParams()["json"]

	return c.Request().Header.Get("Content-Type") == "application/json" || c.Request().Header.Get("accept") == "application/json" || c.QueryParam("type") == "json" || ok
}
<commit_msg>Added X-Content-Type-Options: nosniff to all responses<commit_after>package api

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/gempir/justlog/helix"

	twitch "github.com/gempir/go-twitch-irc"
	"github.com/gempir/justlog/filelog"
	jsoniter "github.com/json-iterator/go"
	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"

	_ "github.com/gempir/justlog/docs"
	"github.com/swaggo/echo-swagger"
)

type Server struct {
	listenAddress string
	logPath       string
	fileLogger    *filelog.Logger
	helixClient   *helix.Client
	channels      []string
}

func NewServer(logPath string, listenAddress string, fileLogger *filelog.Logger, helixClient *helix.Client) Server {
	return Server{
		listenAddress: listenAddress,
		logPath:       logPath,
		fileLogger:    fileLogger,
		helixClient:   helixClient,
		channels:      []string{},
	}
}

func (s *Server) AddChannel(channel string) {
	s.channels = append(s.channels, channel)
}

func (s *Server) Init() {

	e := echo.New()
	e.HideBanner = true

	DefaultCORSConfig := middleware.CORSConfig{
		Skipper:      middleware.DefaultSkipper,
		AllowOrigins: []string{"*"},
		AllowMethods: []string{echo.GET, echo.HEAD, echo.PUT, echo.PATCH, echo.POST, echo.DELETE},
	}
	e.Use(middleware.RemoveTrailingSlashWithConfig(middleware.TrailingSlashConfig{
		RedirectCode: http.StatusMovedPermanently,
	}))
	e.Use(middleware.SecureWithConfig(middleware.SecureConfig{
		XSSProtection:         "", // disabled
		ContentTypeNosniff:    "nosniff",
		XFrameOptions:         "", // disabled
		HSTSMaxAge:            0, // disabled
		ContentSecurityPolicy: "", // disabled
	}))
	e.Use(middleware.CORSWithConfig(DefaultCORSConfig))

	e.GET("/", func(c echo.Context) error {
		return c.Redirect(http.StatusMovedPermanently, "/index.html")
	})
	e.GET("/*", echoSwagger.WrapHandler)
	e.GET("/channelid", s.getAllChannels)

	e.GET("/channel/:channel/user/:username/range", s.getUserLogsRangeByName)
	e.GET("/channelid/:channelid/userid/:userid/range", s.getUserLogsRange)

	e.GET("/channel/:channel/user/:username", s.getLastUserLogsByName)
	e.GET("/channel/:channel/user/:username/:year/:month", s.getUserLogsByName)
	e.GET("/channel/:channel/user/:username/random", s.getRandomQuoteByName)

	e.GET("/channelid/:channelid/user/:userid", s.getLastUserLogs)
	e.GET("/channelid/:channelid/userid/:userid/:year/:month", s.getUserLogs)
	e.GET("/channelid/:channelid/userid/:userid/random", s.getRandomQuote)

	e.GET("/channelid/:channelid/range", s.getChannelLogsRange)
	e.GET("/channel/:channel/range", s.getChannelLogsRangeByName)

	e.GET("/channel/:channel", s.getCurrentChannelLogsByName)
	e.GET("/channel/:channel/:year/:month/:day", s.getChannelLogsByName)
	e.GET("/channelid/:channelid", s.getCurrentChannelLogs)
	e.GET("/channelid/:channelid/:year/:month/:day", s.getChannelLogs)

	e.Logger.Fatal(e.Start(s.listenAddress))
}

var (
	userHourLimit    = 744.0
	channelHourLimit = 24.0
)

type AllChannelsJSON struct {
	Channels []string `json:"channels"`
}

type chatLog struct {
	Messages []chatMessage `json:"messages"`
}

type chatMessage struct {
	Text        string             `json:"text"`
	Username    string             `json:"username"`
	DisplayName string             `json:"displayName"`
	Channel     string             `json:"channel"`
	Timestamp   timestamp          `json:"timestamp"`
	Type        twitch.MessageType `json:"type"`
}

type errorResponse struct {
	Message string `json:"message"`
}

type timestamp struct {
	time.Time
}

func reverse(input []string) []string {
	for i, j := 0, len(input)-1; i < j; i, j = i+1, j-1 {
		input[i], input[j] = input[j], input[i]
	}
	return input
}

func (s *Server) getAllChannels(c echo.Context) error {
	response := new(AllChannelsJSON)
	response.Channels = s.channels

	return c.JSON(http.StatusOK, response)
}

func (t timestamp) MarshalJSON() ([]byte, error) {
	return []byte("\"" + t.UTC().Format(time.RFC3339) + "\""), nil
}

func (t *timestamp) UnmarshalJSON(data []byte) error {
	goTime, err := time.Parse(time.RFC3339, strings.TrimSuffix(strings.TrimPrefix(string(data[:]), "\""), "\""))
	if err != nil {
		return err
	}
	*t = timestamp{
		goTime,
	}
	return nil
}

func parseFromTo(from, to string, limit float64) (time.Time, time.Time, error) {
	var fromTime time.Time
	var toTime time.Time

	if from == "" && to == "" {
		fromTime = time.Now().AddDate(0, -1, 0)
		toTime = time.Now()
	} else if from == "" && to != "" {
		var err error
		toTime, err = parseTimestamp(to)
		if err != nil {
			return fromTime, toTime, fmt.Errorf("Can't parse to timestamp: %s", err)
		}
		fromTime = toTime.AddDate(0, -1, 0)
	} else if from != "" && to == "" {
		var err error
		fromTime, err = parseTimestamp(from)
		if err != nil {
			return fromTime, toTime, fmt.Errorf("Can't parse from timestamp: %s", err)
		}
		toTime = fromTime.AddDate(0, 1, 0)
	} else {
		var err error

		fromTime, err = parseTimestamp(from)
		if err != nil {
			return fromTime, toTime, fmt.Errorf("Can't parse from timestamp: %s", err)
		}
		toTime, err = parseTimestamp(to)
		if err != nil {
			return fromTime, toTime, fmt.Errorf("Can't parse to timestamp: %s", err)
		}

		if toTime.Sub(fromTime).Hours() > limit {
			return fromTime, toTime, errors.New("Timespan too big")
		}
	}

	return fromTime, toTime, nil
}

func writeTextResponse(c echo.Context, cLog *chatLog) error {
	c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextPlainCharsetUTF8)
	c.Response().WriteHeader(http.StatusOK)

	for _, cMessage := range cLog.Messages {
		switch cMessage.Type {
		case twitch.PRIVMSG:
			c.Response().Write([]byte(fmt.Sprintf("[%s] #%s %s: %s\n", cMessage.Timestamp.Format("2006-01-2 15:04:05"), cMessage.Channel, cMessage.Username, cMessage.Text)))
		case twitch.CLEARCHAT:
			c.Response().Write([]byte(fmt.Sprintf("[%s] #%s %s\n", cMessage.Timestamp.Format("2006-01-2 15:04:05"), cMessage.Channel, cMessage.Text)))
		}
	}

	return nil
}

func writeJSONResponse(c echo.Context, logResult *chatLog) error {
	_, stream := c.QueryParams()["stream"]
	if stream {
		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
		c.Response().WriteHeader(http.StatusOK)

		return json.NewEncoder(c.Response()).Encode(logResult)
	}

	json := jsoniter.ConfigCompatibleWithStandardLibrary
	data, err := json.Marshal(logResult)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, err.Error())
	}

	return c.Blob(http.StatusOK, echo.MIMEApplicationJSONCharsetUTF8, data)
}

func parseTimestamp(timestamp string) (time.Time, error) {

	i, err := strconv.ParseInt(timestamp, 10, 64)
	if err != nil {
		return time.Now(), err
	}
	return time.Unix(i, 0), nil
}

func shouldReverse(c echo.Context) bool {
	_, ok := c.QueryParams()["reverse"]

	return c.QueryParam("order") == "reverse" || ok
}

func shouldRespondWithJson(c echo.Context) bool {
	_, ok := c.QueryParams()["json"]

	return c.Request().Header.Get("Content-Type") == "application/json" || c.Request().Header.Get("accept") == "application/json" || c.QueryParam("type") == "json" || ok
}
<|endoftext|>


<commit_before>// Copyright 2013 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package api

import (
	"fmt"
	"github.com/bmizerany/pat"
	"github.com/globocom/config"
	"github.com/globocom/tsuru/app"
	"github.com/globocom/tsuru/log"
	"github.com/globocom/tsuru/provision"
	"net"
	"net/http"
	"os"
)

func fatal(err error) {
	fmt.Fprintln(os.Stderr, err)
	log.Fatal(err)
}

func loadConfig(flags map[string]interface{}) string {
	configFile, ok := flags["config"].(string)
	if !ok {
		configFile = "/etc/tsuru/tsuru.conf"
	}
	err := config.ReadAndWatchConfigFile(configFile)
	if err != nil {
		fatal(err)
	}
	return configFile
}

func RunServer(flags map[string]interface{}) {
	configFile := loadConfig(flags)
	log.Init()
	dry, ok := flags["dry"].(bool)
	if !ok {
		dry = false
	}
	connString, err := config.GetString("database:url")
	if err != nil {
		fatal(err)
	}
	dbName, err := config.GetString("database:name")
	if err != nil {
		fatal(err)
	}
	fmt.Printf("Using the database %q from the server %q.\n\n", dbName, connString)

	m := pat.New()

	m.Get("/schema/app", authorizationRequiredHandler(appSchema))
	m.Get("/schema/service", authorizationRequiredHandler(serviceSchema))
	m.Get("/schema/services", authorizationRequiredHandler(servicesSchema))

	m.Get("/quota/:owner", authorizationRequiredHandler(quotaByOwner))

	m.Get("/services/instances", authorizationRequiredHandler(serviceInstances))
	m.Get("/services/instances/:name", authorizationRequiredHandler(serviceInstance))
	m.Del("/services/instances/:name", authorizationRequiredHandler(removeServiceInstance))
	m.Post("/services/instances", authorizationRequiredHandler(createServiceInstance))
	m.Put("/services/instances/:instance/:app", authorizationRequiredHandler(bindServiceInstance))
	m.Del("/services/instances/:instance/:app", authorizationRequiredHandler(unbindServiceInstance))
	m.Get("/services/instances/:instance/status", authorizationRequiredHandler(serviceInstanceStatus))

	m.Get("/services", authorizationRequiredHandler(serviceList))
	m.Post("/services", authorizationRequiredHandler(serviceCreate))
	m.Put("/services", authorizationRequiredHandler(serviceUpdate))
	m.Del("/services/:name", authorizationRequiredHandler(serviceDelete))
	m.Get("/services/:name", authorizationRequiredHandler(serviceInfo))
	m.Get("/services/:name/doc", authorizationRequiredHandler(serviceDoc))
	m.Put("/services/:name/doc", authorizationRequiredHandler(serviceAddDoc))
	m.Put("/services/:service/:team", authorizationRequiredHandler(grantServiceAccess))
	m.Del("/services/:service/:team", authorizationRequiredHandler(revokeServiceAccess))

	m.Del("/apps/:app", authorizationRequiredHandler(appDelete))
	m.Get("/apps/:app", authorizationRequiredHandler(appInfo))
	m.Post("/apps/:app/cname", authorizationRequiredHandler(setCName))
	m.Del("/apps/:app/cname", authorizationRequiredHandler(unsetCName))
	m.Post("/apps/:app/run", authorizationRequiredHandler(runCommand))
	m.Get("/apps/:app/restart", authorizationRequiredHandler(restart))
	m.Get("/apps/:app/env", authorizationRequiredHandler(getEnv))
	m.Post("/apps/:app/env", authorizationRequiredHandler(setEnv))
	m.Del("/apps/:app/env", authorizationRequiredHandler(unsetEnv))
	m.Get("/apps", authorizationRequiredHandler(appList))
	m.Post("/apps", authorizationRequiredHandler(createApp))
	m.Put("/apps/:app/units", authorizationRequiredHandler(addUnits))
	m.Del("/apps/:app/units", authorizationRequiredHandler(removeUnits))
	m.Put("/apps/:app/:team", authorizationRequiredHandler(grantAppAccess))
	m.Del("/apps/:app/:team", authorizationRequiredHandler(revokeAppAccess))
	m.Get("/apps/:app/log", authorizationRequiredHandler(appLog))
	m.Post("/apps/:app/log", authorizationRequiredHandler(addLog))

	m.Get("/platforms", authorizationRequiredHandler(platformList))

	// These handlers don't use :app on purpose. Using :app means that only
	// the token generate for the given app is valid, but these handlers
	// use a token generated for Gandalf.
	m.Get("/apps/:appname/available", authorizationRequiredHandler(appIsAvailable))
	m.Post("/apps/:appname/repository/clone", authorizationRequiredHandler(cloneRepository))

	if registrationEnabled, _ := config.GetBool("auth:user-registration"); registrationEnabled {
		m.Post("/users", handler(createUser))
	}

	m.Post("/users/:email/password", handler(resetPassword))
	m.Post("/users/:email/tokens", handler(login))
	m.Del("/users/tokens", authorizationRequiredHandler(logout))
	m.Put("/users/password", authorizationRequiredHandler(changePassword))
	m.Del("/users", authorizationRequiredHandler(removeUser))
	m.Get("/users/:email/keys", authorizationRequiredHandler(listKeys))
	m.Post("/users/keys", authorizationRequiredHandler(addKeyToUser))
	m.Del("/users/keys", authorizationRequiredHandler(removeKeyFromUser))

	m.Post("/tokens", adminRequiredHandler(generateAppToken))

	m.Get("/teams", authorizationRequiredHandler(teamList))
	m.Post("/teams", authorizationRequiredHandler(createTeam))
	m.Get("/teams/:name", authorizationRequiredHandler(getTeam))
	m.Del("/teams/:name", authorizationRequiredHandler(removeTeam))
	m.Put("/teams/:team/:user", authorizationRequiredHandler(addUserToTeam))
	m.Del("/teams/:team/:user", authorizationRequiredHandler(removeUserFromTeam))

	m.Get("/healers", authorizationRequiredHandler(healers))
	m.Get("/healers/:healer", authorizationRequiredHandler(healer))

	m.Put("/swap", authorizationRequiredHandler(healers))

	if !dry {
		provisioner, err := config.GetString("provisioner")
		if err != nil {
			fmt.Printf("Warning: %q didn't declare a provisioner, using default provisioner.\n", configFile)
			provisioner = "juju"
		}
		app.Provisioner, err = provision.Get(provisioner)
		if err != nil {
			fatal(err)
		}
		fmt.Printf("Using %q provisioner.\n\n", provisioner)

		listen, err := config.GetString("listen")
		if err != nil {
			fatal(err)
		}
		tls, _ := config.GetBool("use-tls")
		if tls {
			certFile, err := config.GetString("tls:cert-file")
			if err != nil {
				fatal(err)
			}
			keyFile, err := config.GetString("tls:key-file")
			if err != nil {
				fatal(err)
			}
			fmt.Printf("tsuru HTTP/TLS server listening at %s...\n", listen)
			fatal(http.ListenAndServeTLS(listen, certFile, keyFile, m))
		} else {
			listener, err := net.Listen("tcp", listen)
			if err != nil {
				fatal(err)
			}
			fmt.Printf("tsuru HTTP server listening at %s...\n", listen)
			http.Handle("/", m)
			fatal(http.Serve(listener, nil))
		}
	}
}
<commit_msg>api: improve RunServer function<commit_after>// Copyright 2013 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package api

import (
	"fmt"
	"github.com/bmizerany/pat"
	"github.com/globocom/config"
	"github.com/globocom/tsuru/app"
	"github.com/globocom/tsuru/log"
	"github.com/globocom/tsuru/provision"
	"net"
	"net/http"
	"os"
)

func fatal(err error) {
	fmt.Fprintln(os.Stderr, err)
	log.Fatal(err)
}

// RunServer starts Tsuru API server. The dry parameter indicates whether the
// server should run in dry mode, not starting the HTTP listener (for testing
// purposes).
func RunServer(dry bool) {
	connString, err := config.GetString("database:url")
	if err != nil {
		fatal(err)
	}
	dbName, err := config.GetString("database:name")
	if err != nil {
		fatal(err)
	}
	fmt.Printf("Using the database %q from the server %q.\n\n", dbName, connString)

	m := pat.New()

	m.Get("/schema/app", authorizationRequiredHandler(appSchema))
	m.Get("/schema/service", authorizationRequiredHandler(serviceSchema))
	m.Get("/schema/services", authorizationRequiredHandler(servicesSchema))

	m.Get("/quota/:owner", authorizationRequiredHandler(quotaByOwner))

	m.Get("/services/instances", authorizationRequiredHandler(serviceInstances))
	m.Get("/services/instances/:name", authorizationRequiredHandler(serviceInstance))
	m.Del("/services/instances/:name", authorizationRequiredHandler(removeServiceInstance))
	m.Post("/services/instances", authorizationRequiredHandler(createServiceInstance))
	m.Put("/services/instances/:instance/:app", authorizationRequiredHandler(bindServiceInstance))
	m.Del("/services/instances/:instance/:app", authorizationRequiredHandler(unbindServiceInstance))
	m.Get("/services/instances/:instance/status", authorizationRequiredHandler(serviceInstanceStatus))

	m.Get("/services", authorizationRequiredHandler(serviceList))
	m.Post("/services", authorizationRequiredHandler(serviceCreate))
	m.Put("/services", authorizationRequiredHandler(serviceUpdate))
	m.Del("/services/:name", authorizationRequiredHandler(serviceDelete))
	m.Get("/services/:name", authorizationRequiredHandler(serviceInfo))
	m.Get("/services/:name/doc", authorizationRequiredHandler(serviceDoc))
	m.Put("/services/:name/doc", authorizationRequiredHandler(serviceAddDoc))
	m.Put("/services/:service/:team", authorizationRequiredHandler(grantServiceAccess))
	m.Del("/services/:service/:team", authorizationRequiredHandler(revokeServiceAccess))

	m.Del("/apps/:app", authorizationRequiredHandler(appDelete))
	m.Get("/apps/:app", authorizationRequiredHandler(appInfo))
	m.Post("/apps/:app/cname", authorizationRequiredHandler(setCName))
	m.Del("/apps/:app/cname", authorizationRequiredHandler(unsetCName))
	m.Post("/apps/:app/run", authorizationRequiredHandler(runCommand))
	m.Get("/apps/:app/restart", authorizationRequiredHandler(restart))
	m.Get("/apps/:app/env", authorizationRequiredHandler(getEnv))
	m.Post("/apps/:app/env", authorizationRequiredHandler(setEnv))
	m.Del("/apps/:app/env", authorizationRequiredHandler(unsetEnv))
	m.Get("/apps", authorizationRequiredHandler(appList))
	m.Post("/apps", authorizationRequiredHandler(createApp))
	m.Put("/apps/:app/units", authorizationRequiredHandler(addUnits))
	m.Del("/apps/:app/units", authorizationRequiredHandler(removeUnits))
	m.Put("/apps/:app/:team", authorizationRequiredHandler(grantAppAccess))
	m.Del("/apps/:app/:team", authorizationRequiredHandler(revokeAppAccess))
	m.Get("/apps/:app/log", authorizationRequiredHandler(appLog))
	m.Post("/apps/:app/log", authorizationRequiredHandler(addLog))

	m.Get("/platforms", authorizationRequiredHandler(platformList))

	// These handlers don't use :app on purpose. Using :app means that only
	// the token generate for the given app is valid, but these handlers
	// use a token generated for Gandalf.
	m.Get("/apps/:appname/available", authorizationRequiredHandler(appIsAvailable))
	m.Post("/apps/:appname/repository/clone", authorizationRequiredHandler(cloneRepository))

	if registrationEnabled, _ := config.GetBool("auth:user-registration"); registrationEnabled {
		m.Post("/users", handler(createUser))
	}

	m.Post("/users/:email/password", handler(resetPassword))
	m.Post("/users/:email/tokens", handler(login))
	m.Del("/users/tokens", authorizationRequiredHandler(logout))
	m.Put("/users/password", authorizationRequiredHandler(changePassword))
	m.Del("/users", authorizationRequiredHandler(removeUser))
	m.Get("/users/:email/keys", authorizationRequiredHandler(listKeys))
	m.Post("/users/keys", authorizationRequiredHandler(addKeyToUser))
	m.Del("/users/keys", authorizationRequiredHandler(removeKeyFromUser))

	m.Post("/tokens", adminRequiredHandler(generateAppToken))

	m.Get("/teams", authorizationRequiredHandler(teamList))
	m.Post("/teams", authorizationRequiredHandler(createTeam))
	m.Get("/teams/:name", authorizationRequiredHandler(getTeam))
	m.Del("/teams/:name", authorizationRequiredHandler(removeTeam))
	m.Put("/teams/:team/:user", authorizationRequiredHandler(addUserToTeam))
	m.Del("/teams/:team/:user", authorizationRequiredHandler(removeUserFromTeam))

	m.Get("/healers", authorizationRequiredHandler(healers))
	m.Get("/healers/:healer", authorizationRequiredHandler(healer))

	m.Put("/swap", authorizationRequiredHandler(healers))

	if !dry {
		provisioner, err := config.GetString("provisioner")
		if err != nil {
			fmt.Printf("Warning: configuration didn't declare a provisioner, using default provisioner.\n")
			provisioner = "juju"
		}
		app.Provisioner, err = provision.Get(provisioner)
		if err != nil {
			fatal(err)
		}
		fmt.Printf("Using %q provisioner.\n\n", provisioner)

		listen, err := config.GetString("listen")
		if err != nil {
			fatal(err)
		}
		tls, _ := config.GetBool("use-tls")
		if tls {
			certFile, err := config.GetString("tls:cert-file")
			if err != nil {
				fatal(err)
			}
			keyFile, err := config.GetString("tls:key-file")
			if err != nil {
				fatal(err)
			}
			fmt.Printf("tsuru HTTP/TLS server listening at %s...\n", listen)
			fatal(http.ListenAndServeTLS(listen, certFile, keyFile, m))
		} else {
			listener, err := net.Listen("tcp", listen)
			if err != nil {
				fatal(err)
			}
			fmt.Printf("tsuru HTTP server listening at %s...\n", listen)
			http.Handle("/", m)
			fatal(http.Serve(listener, nil))
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"encoding/json"
	"github.com/bmizerany/pq"
	"github.com/gorilla/mux"
	"log"
	"net/http"
	"strconv"
	"time"
)

func main() {
	log.Print("starting ChicagoWorksforYou.com API server")

	router := mux.NewRouter()
	router.HandleFunc("/health_check", HealthCheckHandler)
	router.HandleFunc("/services.json", ServicesHandler)
	router.HandleFunc("/wards/{id}/requests.json", WardRequestsHandler)
	router.HandleFunc("/wards/{id}/counts.json", WardCountsHandler)
	http.ListenAndServe(":5000", router)
}

func WardCountsHandler(response http.ResponseWriter, request *http.Request) {
	// for a given ward, return the number of service requests opened
	// grouped by day, then by service request type
	//
	// Parameters:
	//
	// 	count: 		the number of days of data to return
	// 	end_date: 	date that +count+ is based from.
	// 	service_code: 	the code used by the City of Chicago to categorize service requests
	//
	// Sample API output
	//
	// Note that the end date is June 12, and the results include the end_date.
	// $ curl "http://localhost:5000/wards/10/counts.json?service_code=4fd3b167e750846744000005&count=5&end_date=2013-06-12"
	// {
	//   "2013-06-06": 2,
	//   "2013-06-07": 4,
	//   "2013-06-09": 5,
	//   "2013-06-10": 6,
	//   "2013-06-12": 23
	// }
	//
	vars := mux.Vars(request)
	ward_id := vars["id"]
	params := request.URL.Query()

	db, err := sql.Open("postgres", "dbname=cwfy sslmode=disable")
	if err != nil {
		log.Fatal("Cannot open database connection", err)
	}
	defer db.Close()

	// determine date range. default is last 7 days.
	days, _ := strconv.Atoi(params["count"][0])

	end, _ := time.Parse("2006-01-02", params["end_date"][0])
	end = end.AddDate(0, 0, 1) // inc to the following day
	start := end.AddDate(0, 0, -days)

	log.Printf("fetching counts for ward %s code %s for past %d days", ward_id, params["service_code"][0], days)
	log.Printf("date range is %s to %s", start, end)

	rows, err := db.Query("SELECT COUNT(*), DATE(requested_datetime) as requested_date FROM service_requests WHERE ward = $1 "+
		"AND duplicate IS NULL AND service_code = $2 AND requested_datetime >= $3::date AND requested_datetime <= $4::date "+
		"GROUP BY DATE(requested_datetime) ORDER BY requested_date;", string(ward_id), params["service_code"][0], start, end)
	if err != nil {
		log.Fatal("error fetching data for WardCountsHandler", err)
	}

	type WardCount struct {
		Requested_date time.Time
		Count          int
	}

	var counts []WardCount
	for rows.Next() {
		wc := WardCount{}
		if err := rows.Scan(&wc.Count, &wc.Requested_date); err != nil {
			log.Print("error reading row of ward count", err)
		}

		// trunc the requested time to just date
		counts = append(counts, wc)
	}

	resp := make(map[string]int)

	for _, c := range counts {
		key := c.Requested_date.Format("2006-01-02")
		log.Print("key: ", key)
		resp[key] = c.Count
	}

	jsn, _ := json.MarshalIndent(resp, "", "  ")
	response.Write(jsn)
}

func WardRequestsHandler(response http.ResponseWriter, request *http.Request) {
	// for a given ward, return recent service requests

	vars := mux.Vars(request)
	ward_id := vars["id"]

	log.Print("fetch requests for ward ", ward_id)

	// open database
	db, err := sql.Open("postgres", "dbname=cwfy sslmode=disable")
	if err != nil {
		log.Fatal("Cannot open database connection", err)
	}
	defer db.Close()

	rows, err := db.Query("SELECT lat,long,ward,police_district,service_request_id,status,service_name,service_code,agency_responsible,address,channel,media_url,requested_datetime,updated_datetime,created_at,updated_at,duplicate,parent_service_request_id,id FROM service_requests WHERE duplicate IS NULL AND ward = $1 ORDER BY updated_at DESC LIMIT 100;", ward_id)

	if err != nil {
		log.Fatal("error fetching data for WardRequestsHandler", err)
	}

	type Open311RequestRow struct {
		Lat, Long                                                                                float64
		Ward, Police_district, Id                                                                                int
		Service_request_id, Status, Service_name, Service_code, Agency_responsible, Address, Channel, Media_url, Duplicate, Parent_service_request_id sql.NullString
		Requested_datetime, Updated_datetime, Created_at, Updated_at                                                                                pq.NullTime // FIXME: should these be proper time objects?
		Extended_attributes                                                                                map[string]interface{}
	}

	var result []Open311RequestRow

	for rows.Next() {
		var row Open311RequestRow
		if err := rows.Scan(&row.Lat, &row.Long, &row.Ward, &row.Police_district,
			&row.Service_request_id, &row.Status, &row.Service_name,
			&row.Service_code, &row.Agency_responsible, &row.Address,
			&row.Channel, &row.Media_url, &row.Requested_datetime,
			&row.Updated_datetime, &row.Created_at, &row.Updated_at,
			&row.Duplicate, &row.Parent_service_request_id,
			&row.Id); err != nil {
			log.Fatal("error reading row", err)
		}

		result = append(result, row)
	}

	jsn, _ := json.MarshalIndent(result, "", "  ")
	response.Write(jsn)
}

func ServicesHandler(response http.ResponseWriter, request *http.Request) {
	// return counts of requests, grouped by service name
	//
	// Sample output:
	//
	// [
	//   {
	//     "Count": 1139,
	//     "Service_code": "4fd3b167e750846744000005",
	//     "Service_name": "Graffiti Removal"
	//   },
	//   {
	//     "Count": 25,
	//     "Service_code": "4fd6e4ece750840569000019",
	//     "Service_name": "Restaurant Complaint"
	//   },
	//
	//  ... snip ...
	//
	// ]

	type ServicesCount struct {
		Count        int
		Service_code string
		Service_name string
	}

	var services []ServicesCount

	// open database
	db, err := sql.Open("postgres", "dbname=cwfy sslmode=disable")
	if err != nil {
		log.Fatal("Cannot open database connection", err)
	}
	defer db.Close()

	rows, err := db.Query("SELECT COUNT(*), service_code, service_name FROM service_requests WHERE duplicate IS NULL GROUP BY service_code,service_name;")

	if err != nil {
		log.Fatal("error fetching data for ServicesHandler", err)
	}

	for rows.Next() {
		var count int
		var service_code, service_name string

		if err := rows.Scan(&count, &service_code, &service_name); err != nil {
			log.Fatal("error reading row", err)
		}

		row := ServicesCount{Count: count, Service_code: service_code, Service_name: service_name}
		services = append(services, row)
	}

	jsn, _ := json.MarshalIndent(services, "", "  ")
	response.Write(jsn)
}

func HealthCheckHandler(response http.ResponseWriter, request *http.Request) {
	response.Header().Add("Content-type", "application/json")

	type HealthCheck struct {
		Count    int
		Database bool
		Healthy  bool
	}

	// open database
	db, err := sql.Open("postgres", "dbname=cwfy sslmode=disable")
	if err != nil {
		log.Fatal("Cannot open database connection", err)
	}
	defer db.Close()

	health_check := HealthCheck{}

	health_check.Database = db.Ping() == nil

	rows, _ := db.Query("SELECT COUNT(*) FROM service_requests;")
	for rows.Next() {
		if err := rows.Scan(&health_check.Count); err != nil {
			log.Fatal("error fetching count", err)
		}
	}

	// calculate overall health
	health_check.Healthy = health_check.Count > 0 && health_check.Database

	log.Printf("health_check: %+v", health_check)
	if !health_check.Healthy {
		log.Printf("health_check failed")
	}
	jsn, _ := json.Marshal(health_check)
	response.Write(jsn)
}
<commit_msg>cleanup how DB connection is handled<commit_after>package main

import (
	"database/sql"
	"encoding/json"
	"github.com/bmizerany/pq"
	"github.com/gorilla/mux"
	"log"
	"net/http"
	"strconv"
	"time"
)

type Api struct {
        Db *sql.DB
        Version string
}

var api Api

func init(){
        // version
        api.Version = "0.0.1"

        // setup database connection
	db, err := sql.Open("postgres", "dbname=cwfy sslmode=disable")
	if err != nil {
		log.Fatal("Cannot open database connection", err)
	}
	api.Db = db
}

func main() {
	log.Print("starting ChicagoWorksforYou.com API server")

	router := mux.NewRouter()
	router.HandleFunc("/health_check", HealthCheckHandler)
	router.HandleFunc("/services.json", ServicesHandler)
	router.HandleFunc("/wards/{id}/requests.json", WardRequestsHandler)
	router.HandleFunc("/wards/{id}/counts.json", WardCountsHandler)
	http.ListenAndServe(":5000", router)
}

func WardCountsHandler(response http.ResponseWriter, request *http.Request) {
	// for a given ward, return the number of service requests opened
	// grouped by day, then by service request type
	//
	// Parameters:
	//
	// 	count: 		the number of days of data to return
	// 	end_date: 	date that +count+ is based from.
	// 	service_code: 	the code used by the City of Chicago to categorize service requests
	//
	// Sample API output
	//
	// Note that the end date is June 12, and the results include the end_date.
	// $ curl "http://localhost:5000/wards/10/counts.json?service_code=4fd3b167e750846744000005&count=5&end_date=2013-06-12"
	// {
	//   "2013-06-06": 2,
	//   "2013-06-07": 4,
	//   "2013-06-09": 5,
	//   "2013-06-10": 6,
	//   "2013-06-12": 23
	// }
	//
	vars := mux.Vars(request)
	ward_id := vars["id"]
	params := request.URL.Query()

	// determine date range. default is last 7 days.
	days, _ := strconv.Atoi(params["count"][0])

	end, _ := time.Parse("2006-01-02", params["end_date"][0])
	end = end.AddDate(0, 0, 1) // inc to the following day
	start := end.AddDate(0, 0, -days)

	log.Printf("fetching counts for ward %s code %s for past %d days", ward_id, params["service_code"][0], days)
	log.Printf("date range is %s to %s", start, end)

	rows, err := api.Db.Query("SELECT COUNT(*), DATE(requested_datetime) as requested_date FROM service_requests WHERE ward = $1 "+
		"AND duplicate IS NULL AND service_code = $2 AND requested_datetime >= $3::date AND requested_datetime <= $4::date "+
		"GROUP BY DATE(requested_datetime) ORDER BY requested_date;", string(ward_id), params["service_code"][0], start, end)
	if err != nil {
		log.Fatal("error fetching data for WardCountsHandler", err)
	}

	type WardCount struct {
		Requested_date time.Time
		Count          int
	}

	var counts []WardCount
	for rows.Next() {
		wc := WardCount{}
		if err := rows.Scan(&wc.Count, &wc.Requested_date); err != nil {
			log.Print("error reading row of ward count", err)
		}

		// trunc the requested time to just date
		counts = append(counts, wc)
	}

	resp := make(map[string]int)

	for _, c := range counts {
		key := c.Requested_date.Format("2006-01-02")
		log.Print("key: ", key)
		resp[key] = c.Count
	}

	jsn, _ := json.MarshalIndent(resp, "", "  ")
	response.Write(jsn)
}

func WardRequestsHandler(response http.ResponseWriter, request *http.Request) {
	// for a given ward, return recent service requests

	vars := mux.Vars(request)
	ward_id := vars["id"]

	log.Print("fetch requests for ward ", ward_id)

	rows, err := api.Db.Query("SELECT lat,long,ward,police_district,service_request_id,status,service_name,service_code,agency_responsible,address,channel,media_url,requested_datetime,updated_datetime,created_at,updated_at,duplicate,parent_service_request_id,id FROM service_requests WHERE duplicate IS NULL AND ward = $1 ORDER BY updated_at DESC LIMIT 100;", ward_id)

	if err != nil {
		log.Fatal("error fetching data for WardRequestsHandler", err)
	}

	type Open311RequestRow struct {
		Lat, Long                                                                                float64
		Ward, Police_district, Id                                                                                int
		Service_request_id, Status, Service_name, Service_code, Agency_responsible, Address, Channel, Media_url, Duplicate, Parent_service_request_id sql.NullString
		Requested_datetime, Updated_datetime, Created_at, Updated_at                                                                                pq.NullTime // FIXME: should these be proper time objects?
		Extended_attributes                                                                                map[string]interface{}
	}

	var result []Open311RequestRow

	for rows.Next() {
		var row Open311RequestRow
		if err := rows.Scan(&row.Lat, &row.Long, &row.Ward, &row.Police_district,
			&row.Service_request_id, &row.Status, &row.Service_name,
			&row.Service_code, &row.Agency_responsible, &row.Address,
			&row.Channel, &row.Media_url, &row.Requested_datetime,
			&row.Updated_datetime, &row.Created_at, &row.Updated_at,
			&row.Duplicate, &row.Parent_service_request_id,
			&row.Id); err != nil {
			log.Fatal("error reading row", err)
		}

		result = append(result, row)
	}

	jsn, _ := json.MarshalIndent(result, "", "  ")
	response.Write(jsn)
}

func ServicesHandler(response http.ResponseWriter, request *http.Request) {
	// return counts of requests, grouped by service name
	//
	// Sample output:
	//
	// [
	//   {
	//     "Count": 1139,
	//     "Service_code": "4fd3b167e750846744000005",
	//     "Service_name": "Graffiti Removal"
	//   },
	//   {
	//     "Count": 25,
	//     "Service_code": "4fd6e4ece750840569000019",
	//     "Service_name": "Restaurant Complaint"
	//   },
	//
	//  ... snip ...
	//
	// ]

	type ServicesCount struct {
		Count        int
		Service_code string
		Service_name string
	}

	var services []ServicesCount

	rows, err := api.Db.Query("SELECT COUNT(*), service_code, service_name FROM service_requests WHERE duplicate IS NULL GROUP BY service_code,service_name;")

	if err != nil {
		log.Fatal("error fetching data for ServicesHandler", err)
	}

	for rows.Next() {
		var count int
		var service_code, service_name string

		if err := rows.Scan(&count, &service_code, &service_name); err != nil {
			log.Fatal("error reading row", err)
		}

		row := ServicesCount{Count: count, Service_code: service_code, Service_name: service_name}
		services = append(services, row)
	}

	jsn, _ := json.MarshalIndent(services, "", "  ")
	response.Write(jsn)
}

func HealthCheckHandler(response http.ResponseWriter, request *http.Request) {
	response.Header().Add("Content-type", "application/json")

	type HealthCheck struct {
		Count    int
		Database bool
		Healthy  bool
		Version  string
	}

	health_check := HealthCheck{Version: api.Version}

	health_check.Database = api.Db.Ping() == nil

	rows, _ := api.Db.Query("SELECT COUNT(*) FROM service_requests;")
	for rows.Next() {
		if err := rows.Scan(&health_check.Count); err != nil {
			log.Fatal("error fetching count", err)
		}
	}

	// calculate overall health
	health_check.Healthy = health_check.Count > 0 && health_check.Database

	log.Printf("health_check: %+v", health_check)
	if !health_check.Healthy {
		log.Printf("health_check failed")
	}
	jsn, _ := json.MarshalIndent(health_check, "", "  ")
	response.Write(jsn)
}<|endoftext|>