<commit_before>/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package slackevents

import (
	"fmt"
	"regexp"

	"k8s.io/test-infra/prow/github"
	"k8s.io/test-infra/prow/plugins"
)

const (
	pluginName = "slackevents"
)

var sigMatcher = regexp.MustCompile(`(?m)@kubernetes/sig-([\w-]*)-(misc|test-failures|bugs|feature-requests|proposals|pr-reviews|api-reviews)`)

type slackClient interface {
	WriteMessage(text string, channel string) error
}

type githubClient interface {
	BotName() (string, error)
}

type client struct {
	GithubClient githubClient
	SlackClient  slackClient
	SlackConfig  plugins.Slack
}

func init() {
	plugins.RegisterPushEventHandler(pluginName, handlePush)
	plugins.RegisterGenericCommentHandler(pluginName, handleComment)
}

func handleComment(pc plugins.PluginClient, e github.GenericCommentEvent) error {
	c := client{
		SlackConfig: pc.PluginConfig.Slack,
		SlackClient: pc.SlackClient,
	}
	return echoToSlack(c, e)
}

func handlePush(pc plugins.PluginClient, pe github.PushEvent) error {
	c := client{
		SlackConfig: pc.PluginConfig.Slack,
		SlackClient: pc.SlackClient,
	}
	return notifyOnSlackIfManualMerge(c, pe)
}

func notifyOnSlackIfManualMerge(pc client, pe github.PushEvent) error {
	//Fetch slackevent configuration for the repo we received the merge event.
	if se := getSlackEvent(pc.SlackConfig.MergeWarnings, pe.Repo.Owner.Login, pe.Repo.Name); se != nil {
		//If the slackevent whitelist has the merge user then no need to send a message.
		if !stringInArray(pe.Pusher.Name, se.WhiteList) && !stringInArray(pe.Sender.Login, se.WhiteList) {
			message := fmt.Sprintf("Warning: <@%s> manually merged %s", pe.Sender.Login, pe.Compare)
			for _, channel := range se.Channels {
				if err := pc.SlackClient.WriteMessage(message, channel); err != nil {
					return err
				}
			}
		}
	}
	return nil
}

func getSlackEvent(slackEvents []plugins.MergeWarning, org, repo string) *plugins.MergeWarning {
	for _, se := range slackEvents {
		if stringInArray(org, se.Repos) || stringInArray(fmt.Sprintf("%s/%s", org, repo), se.Repos) {
			return &se
		}
	}
	return nil
}

func stringInArray(str string, list []string) bool {
	for _, v := range list {
		if v == str {
			return true
		}
	}
	return false
}

func echoToSlack(pc client, e github.GenericCommentEvent) error {
	// Ignore bot comments and comments that aren't new.
	botName, err := pc.GithubClient.BotName()
	if err != nil {
		return err
	}
	if e.User.Login == botName {
		return nil
	}
	if e.Action != github.GenericCommentActionCreated {
		return nil
	}

	sigMatches := sigMatcher.FindAllStringSubmatch(e.Body, -1)

	for _, match := range sigMatches {
		sig := "sig-" + match[1]
		// Check if this sig is a slack channel that should be messaged.
		found := false
		for _, channel := range pc.SlackConfig.MentionChannels {
			if channel == sig {
				found = true
				break
			}
		}
		if !found {
			continue
		}

		msg := fmt.Sprintf("%s was mentioned by <@%s> on Github. (%s)\n>>>%s", sig, e.User.Login, e.HTMLURL, e.Body)
		if err := pc.SlackClient.WriteMessage(msg, sig); err != nil {
			return fmt.Errorf("Failed to send message on slack channel: %q with message %q. Err: %v", sig, msg, err)
		}
	}
	return nil
}
<commit_msg>Fix bug: unset github client in slackevents.<commit_after>/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package slackevents

import (
	"fmt"
	"regexp"

	"k8s.io/test-infra/prow/github"
	"k8s.io/test-infra/prow/plugins"
)

const (
	pluginName = "slackevents"
)

var sigMatcher = regexp.MustCompile(`(?m)@kubernetes/sig-([\w-]*)-(misc|test-failures|bugs|feature-requests|proposals|pr-reviews|api-reviews)`)

type slackClient interface {
	WriteMessage(text string, channel string) error
}

type githubClient interface {
	BotName() (string, error)
}

type client struct {
	GithubClient githubClient
	SlackClient  slackClient
	SlackConfig  plugins.Slack
}

func init() {
	plugins.RegisterPushEventHandler(pluginName, handlePush)
	plugins.RegisterGenericCommentHandler(pluginName, handleComment)
}

func handleComment(pc plugins.PluginClient, e github.GenericCommentEvent) error {
	c := client{
		GithubClient: pc.GitHubClient,
		SlackConfig:  pc.PluginConfig.Slack,
		SlackClient:  pc.SlackClient,
	}
	return echoToSlack(c, e)
}

func handlePush(pc plugins.PluginClient, pe github.PushEvent) error {
	c := client{
		GithubClient: pc.GitHubClient,
		SlackConfig:  pc.PluginConfig.Slack,
		SlackClient:  pc.SlackClient,
	}
	return notifyOnSlackIfManualMerge(c, pe)
}

func notifyOnSlackIfManualMerge(pc client, pe github.PushEvent) error {
	//Fetch slackevent configuration for the repo we received the merge event.
	if se := getSlackEvent(pc.SlackConfig.MergeWarnings, pe.Repo.Owner.Login, pe.Repo.Name); se != nil {
		//If the slackevent whitelist has the merge user then no need to send a message.
		if !stringInArray(pe.Pusher.Name, se.WhiteList) && !stringInArray(pe.Sender.Login, se.WhiteList) {
			message := fmt.Sprintf("Warning: <@%s> manually merged %s", pe.Sender.Login, pe.Compare)
			for _, channel := range se.Channels {
				if err := pc.SlackClient.WriteMessage(message, channel); err != nil {
					return err
				}
			}
		}
	}
	return nil
}

func getSlackEvent(slackEvents []plugins.MergeWarning, org, repo string) *plugins.MergeWarning {
	for _, se := range slackEvents {
		if stringInArray(org, se.Repos) || stringInArray(fmt.Sprintf("%s/%s", org, repo), se.Repos) {
			return &se
		}
	}
	return nil
}

func stringInArray(str string, list []string) bool {
	for _, v := range list {
		if v == str {
			return true
		}
	}
	return false
}

func echoToSlack(pc client, e github.GenericCommentEvent) error {
	// Ignore bot comments and comments that aren't new.
	botName, err := pc.GithubClient.BotName()
	if err != nil {
		return err
	}
	if e.User.Login == botName {
		return nil
	}
	if e.Action != github.GenericCommentActionCreated {
		return nil
	}

	sigMatches := sigMatcher.FindAllStringSubmatch(e.Body, -1)

	for _, match := range sigMatches {
		sig := "sig-" + match[1]
		// Check if this sig is a slack channel that should be messaged.
		found := false
		for _, channel := range pc.SlackConfig.MentionChannels {
			if channel == sig {
				found = true
				break
			}
		}
		if !found {
			continue
		}

		msg := fmt.Sprintf("%s was mentioned by <@%s> on Github. (%s)\n>>>%s", sig, e.User.Login, e.HTMLURL, e.Body)
		if err := pc.SlackClient.WriteMessage(msg, sig); err != nil {
			return fmt.Errorf("Failed to send message on slack channel: %q with message %q. Err: %v", sig, msg, err)
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package v1

import "git.zxq.co/ripple/rippleapi/common"

type donorInfoResponse struct {
	common.ResponseBase
	HasDonor   bool                 `json:"has_donor"`
	Expiration common.UnixTimestamp `json:"expiration"`
}

// UsersSelfDonorInfoGET returns information about the users' donor status
func UsersSelfDonorInfoGET(md common.MethodData) common.CodeMessager {
	var r donorInfoResponse
	var privileges uint64
	err := md.DB.QueryRow("SELECT privileges, donor_expire FROM users WHERE id = ?", md.ID()).
		Scan(&privileges, &r.Expiration)
	if err != nil {
		md.Err(err)
		return Err500
	}
	r.HasDonor = common.UserPrivileges(privileges)&common.UserPrivilegeDonor > 0
	r.Code = 200
	return r
}

type favouriteModeResponse struct {
	common.ResponseBase
	FavouriteMode int `json:"favourite_mode"`
}

// UsersSelfFavouriteModeGET gets the current user's favourite mode
func UsersSelfFavouriteModeGET(md common.MethodData) common.CodeMessager {
	var f favouriteModeResponse
	f.Code = 200
	if md.ID() == 0 {
		return f
	}
	err := md.DB.QueryRow("SELECT users_stats.favourite_mode FROM users_stats WHERE id = ?", md.ID()).
		Scan(&f.FavouriteMode)
	if err != nil {
		md.Err(err)
		return Err500
	}
	return f
}

type userSettingsData struct {
	UsernameAKA   string `json:"username_aka"`
	FavouriteMode *int   `json:"favourite_mode"`
	CustomBadge   struct {
		singleBadge
		Show *bool `json:"show"`
	} `json:"custom_badge"`
	PlayStyle *int `json:"play_style"`
}

// UsersSelfSettingsPOST allows to modify information about the current user.
func UsersSelfSettingsPOST(md common.MethodData) common.CodeMessager {
	var d userSettingsData
	md.RequestData.Unmarshal(&d)
	q := new(common.UpdateQuery).
		Add("s.username_aka", d.UsernameAKA).
		Add("s.favourite_mode", d.FavouriteMode).
		Add("s.custom_badge_name", d.CustomBadge.Name).
		Add("s.custom_badge_icon", d.CustomBadge.Icon).
		Add("s.show_custom_badge", d.CustomBadge.Show).
		Add("s.play_style", d.PlayStyle)
	_, err := md.DB.Exec("UPDATE users u, users_stats s SET "+q.Fields()+" WHERE s.id = u.id AND u.id = ?", append(q.Parameters, md.ID())...)
	if err != nil {
		md.Err(err)
		return Err500
	}
	return UsersSelfSettingsGET(md)
}

type userSettingsResponse struct {
	common.ResponseBase
	ID       int    `json:"id"`
	Username string `json:"username"`
	userSettingsData
}

// UsersSelfSettingsGET allows to get "sensitive" information about the current user.
func UsersSelfSettingsGET(md common.MethodData) common.CodeMessager {
	var r userSettingsResponse
	var ccb bool
	r.Code = 200
	err := md.DB.QueryRow(`
SELECT
	u.id, u.username,
	u.email, s.username_aka, s.favourite_mode,
	s.show_custom_badge, s.custom_badge_icon,
	s.custom_badge_name, s.can_custom_badge,
	s.play_style
FROM users u
LEFT JOIN users_stats s ON u.id = s.id
WHERE u.id = ?`, md.ID()).Scan(
		&r.ID, &r.Username,
		&r.Email, &r.UsernameAKA, &r.FavouriteMode,
		&r.CustomBadge.Show, &r.CustomBadge.Icon,
		&r.CustomBadge.Name, &ccb,
		&r.PlayStyle,
	)
	if err != nil {
		md.Err(err)
		return Err500
	}
	if !ccb {
		r.CustomBadge = struct {
			singleBadge
			Show *bool `json:"show"`
		}{}
	}
	return r
}
<commit_msg>Add back r.Email<commit_after>package v1

import "git.zxq.co/ripple/rippleapi/common"

type donorInfoResponse struct {
	common.ResponseBase
	HasDonor   bool                 `json:"has_donor"`
	Expiration common.UnixTimestamp `json:"expiration"`
}

// UsersSelfDonorInfoGET returns information about the users' donor status
func UsersSelfDonorInfoGET(md common.MethodData) common.CodeMessager {
	var r donorInfoResponse
	var privileges uint64
	err := md.DB.QueryRow("SELECT privileges, donor_expire FROM users WHERE id = ?", md.ID()).
		Scan(&privileges, &r.Expiration)
	if err != nil {
		md.Err(err)
		return Err500
	}
	r.HasDonor = common.UserPrivileges(privileges)&common.UserPrivilegeDonor > 0
	r.Code = 200
	return r
}

type favouriteModeResponse struct {
	common.ResponseBase
	FavouriteMode int `json:"favourite_mode"`
}

// UsersSelfFavouriteModeGET gets the current user's favourite mode
func UsersSelfFavouriteModeGET(md common.MethodData) common.CodeMessager {
	var f favouriteModeResponse
	f.Code = 200
	if md.ID() == 0 {
		return f
	}
	err := md.DB.QueryRow("SELECT users_stats.favourite_mode FROM users_stats WHERE id = ?", md.ID()).
		Scan(&f.FavouriteMode)
	if err != nil {
		md.Err(err)
		return Err500
	}
	return f
}

type userSettingsData struct {
	UsernameAKA   string `json:"username_aka"`
	FavouriteMode *int   `json:"favourite_mode"`
	CustomBadge   struct {
		singleBadge
		Show *bool `json:"show"`
	} `json:"custom_badge"`
	PlayStyle *int `json:"play_style"`
}

// UsersSelfSettingsPOST allows to modify information about the current user.
func UsersSelfSettingsPOST(md common.MethodData) common.CodeMessager {
	var d userSettingsData
	md.RequestData.Unmarshal(&d)
	q := new(common.UpdateQuery).
		Add("s.username_aka", d.UsernameAKA).
		Add("s.favourite_mode", d.FavouriteMode).
		Add("s.custom_badge_name", d.CustomBadge.Name).
		Add("s.custom_badge_icon", d.CustomBadge.Icon).
		Add("s.show_custom_badge", d.CustomBadge.Show).
		Add("s.play_style", d.PlayStyle)
	_, err := md.DB.Exec("UPDATE users u, users_stats s SET "+q.Fields()+" WHERE s.id = u.id AND u.id = ?", append(q.Parameters, md.ID())...)
	if err != nil {
		md.Err(err)
		return Err500
	}
	return UsersSelfSettingsGET(md)
}

type userSettingsResponse struct {
	common.ResponseBase
	ID       int    `json:"id"`
	Username string `json:"username"`
	Email    string `json:"email"`
	userSettingsData
}

// UsersSelfSettingsGET allows to get "sensitive" information about the current user.
func UsersSelfSettingsGET(md common.MethodData) common.CodeMessager {
	var r userSettingsResponse
	var ccb bool
	r.Code = 200
	err := md.DB.QueryRow(`
SELECT
	u.id, u.username,
	u.email, s.username_aka, s.favourite_mode,
	s.show_custom_badge, s.custom_badge_icon,
	s.custom_badge_name, s.can_custom_badge,
	s.play_style
FROM users u
LEFT JOIN users_stats s ON u.id = s.id
WHERE u.id = ?`, md.ID()).Scan(
		&r.ID, &r.Username,
		&r.Email, &r.UsernameAKA, &r.FavouriteMode,
		&r.CustomBadge.Show, &r.CustomBadge.Icon,
		&r.CustomBadge.Name, &ccb,
		&r.PlayStyle,
	)
	if err != nil {
		md.Err(err)
		return Err500
	}
	if !ccb {
		r.CustomBadge = struct {
			singleBadge
			Show *bool `json:"show"`
		}{}
	}
	return r
}
<|endoftext|>


<commit_before>// Copyright 2017 Grigory Zubankov. All rights reserved.
// Use of this source code is governed by a MIT license
// that can be found in the LICENSE file.
//

package zerodt

import (
	"context"
	"net/http"
	"os"
	"os/signal"
	"sync"
	"time"
)

// App specifies functions to control passed HTTP servers.
type App struct {
	PreServeFn         func(inherited bool) error
	CompleteShutdownFn func()
	PreParentExitFn    func()
	servers            []*http.Server
}

// NewApp returns a new instance of App.
func NewApp(servers ...*http.Server) *App {
	return &App{nil, nil, nil, servers}
}

// ListenAndServe calls ListenAndServe for all servers and returns first error if happens or nil.
func (a *App) ListenAndServe() error {
	var sigWG sync.WaitGroup
	sigWG.Add(1)
	sigCtx, sigCancelFunc := context.WithCancel(context.Background())
	go a.handleSignals(sigCtx, &sigWG)

	errs := make(chan error)
	for _, server := range a.servers {
		server := server
		go func() {
			errs <- server.ListenAndServe()
		}()
	}

	var err error
	for i := 0; i < len(a.servers); i++ {
		e := <-errs
		if e != nil && e != http.ErrServerClosed && err == nil {
			err = e
		}
	}

	sigCancelFunc()
	sigWG.Wait()

	return err
}

// Shutdown calls Shutdown for all server and returns first error if happens or nil.
func (a *App) Shutdown() error {
	errs := make(chan error)
	for _, server := range a.servers {
		server := server
		go func() {
			errs <- server.Shutdown(context.Background())
		}()
	}

	var err error
	for i := 0; i < len(a.servers); i++ {
		e := <-errs
		if e != nil && err == nil {
			err = e
		}
	}

	return err
}

// SetWaitParentShutdownTimeout does nothing.
func (a *App) SetWaitParentShutdownTimeout(d time.Duration) {
}

// SetWaitChildTimeout does nothing.
func (a *App) SetWaitChildTimeout(d time.Duration) {
}

func (a *App) handleSignals(ctx context.Context, wg *sync.WaitGroup) {
	defer logger.Printf("stop handling signals")
	defer wg.Done()

	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt)
	defer signal.Stop(signals)

	for {
		select {
		// Exit.
		case <-ctx.Done():
			return
		// OS signal.
		case s := <-signals:
			logger.Printf("signal: %v", s)
			switch s {
			// Shutdown servers. No exit here.
			case os.Interrupt:
				a.Shutdown()
			}
		}
	}
}
<commit_msg>Signature compatibility of Shutdown function for Windows fixed.<commit_after>// Copyright 2017 Grigory Zubankov. All rights reserved.
// Use of this source code is governed by a MIT license
// that can be found in the LICENSE file.
//

package zerodt

import (
	"context"
	"net/http"
	"os"
	"os/signal"
	"sync"
	"time"
)

// App specifies functions to control passed HTTP servers.
type App struct {
	PreServeFn         func(inherited bool) error
	CompleteShutdownFn func()
	PreParentExitFn    func()
	servers            []*http.Server
}

// NewApp returns a new instance of App.
func NewApp(servers ...*http.Server) *App {
	return &App{nil, nil, nil, servers}
}

// ListenAndServe calls ListenAndServe for all servers and returns first error if happens or nil.
func (a *App) ListenAndServe() error {
	var sigWG sync.WaitGroup
	sigWG.Add(1)
	sigCtx, sigCancelFunc := context.WithCancel(context.Background())
	go a.handleSignals(sigCtx, &sigWG)

	errs := make(chan error)
	for _, server := range a.servers {
		server := server
		go func() {
			errs <- server.ListenAndServe()
		}()
	}

	var err error
	for i := 0; i < len(a.servers); i++ {
		e := <-errs
		if e != nil && e != http.ErrServerClosed && err == nil {
			err = e
		}
	}

	sigCancelFunc()
	sigWG.Wait()

	return err
}

// Shutdown calls Shutdown for all server and returns first error if happens or nil.
func (a *App) Shutdown() {
	errs := make(chan error)
	for _, server := range a.servers {
		server := server
		go func() {
			err := server.Shutdown(context.Background())
			if err != nil {
				logger.Printf("server %s has been shutdown with: %v", server.Addr, err)
			} else {
				logger.Printf("server %s has been shutdown", server.Addr)
			}
			errs <- err
		}()
	}

	for i := 0; i < len(a.servers); i++ {
		<-errs
	}
}

// SetWaitParentShutdownTimeout does nothing.
func (a *App) SetWaitParentShutdownTimeout(d time.Duration) {
}

// SetWaitChildTimeout does nothing.
func (a *App) SetWaitChildTimeout(d time.Duration) {
}

func (a *App) handleSignals(ctx context.Context, wg *sync.WaitGroup) {
	defer logger.Printf("stop handling signals")
	defer wg.Done()

	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt)
	defer signal.Stop(signals)

	for {
		select {
		// Exit.
		case <-ctx.Done():
			return
		// OS signal.
		case s := <-signals:
			logger.Printf("signal: %v", s)
			switch s {
			// Shutdown servers. No exit here.
			case os.Interrupt:
				a.Shutdown()
			}
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"log"

	"github.com/jroimartin/gocui"
)

func layout(g *gocui.Gui) error {
	maxX, maxY := g.Size()
	if _, err := g.SetView("side", -1, -1, 30, maxY-5); err != nil {
		return err
	}
	if _, err := g.SetView("main", 30, -1, maxX, maxY-5); err != nil {
		return err
	}
	if _, err := g.SetView("cmdline", -1, maxY-5, maxX, maxY); err != nil {
		return err
	}
	return nil
}

func focusMain(g *gocui.Gui, v *gocui.View) error {
	return g.SetCurrentView("main")
}

func focusSide(g *gocui.Gui, v *gocui.View) error {
	return g.SetCurrentView("side")

}

func focusCmdLine(g *gocui.Gui, v *gocui.View) error {
	return g.SetCurrentView("cmdline")

}

func keybindings(g *gocui.Gui) error {
	if err := g.SetKeybinding("", gocui.KeyCtrlM, 0, focusMain); err != nil {
		return err
	}
	if err := g.SetKeybinding("", gocui.KeyCtrlS, 0, focusSide); err != nil {
		return err
	}
	if err := g.SetKeybinding("", gocui.KeyCtrlL, 0, focusCmdLine); err != nil {
		return err
	}
	if err := g.SetKeybinding("", gocui.KeyCtrlC, 0, quit); err != nil {
		return err
	}
	if err := g.SetKeybinding("main", 'q', 0, quit); err != nil {
		return err
	}
	return nil
}

func start(g *gocui.Gui) error {
	if err := g.SetCurrentView("main"); err != nil {
		return err
	}
	return nil
}

func quit(g *gocui.Gui, v *gocui.View) error {
	return gocui.ErrorQuit
}

func main() {
	var err error

	g := gocui.NewGui()
	if err := g.Init(); err != nil {
		log.Panicln(err)
	}
	defer g.Close()

	g.Layout = layout
	g.Start = start
	g.ShowCursor = true

	if err := keybindings(g); err != nil {
		log.Panicln(err)
	}

	err = g.MainLoop()
	if err != nil && err != gocui.ErrorQuit {
		log.Panicln(err)
	}
}
<commit_msg>Minor fix in demo2.go<commit_after>package main

import (
	"log"

	"github.com/jroimartin/gocui"
)

func layout(g *gocui.Gui) error {
	maxX, maxY := g.Size()
	if _, err := g.SetView("side", -1, -1, 30, maxY-5); err != nil {
		return err
	}
	if _, err := g.SetView("main", 30, -1, maxX, maxY-5); err != nil {
		return err
	}
	if _, err := g.SetView("cmdline", -1, maxY-5, maxX, maxY); err != nil {
		return err
	}
	return nil
}

func focusMain(g *gocui.Gui, v *gocui.View) error {
	return g.SetCurrentView("main")
}

func focusSide(g *gocui.Gui, v *gocui.View) error {
	return g.SetCurrentView("side")

}

func focusCmdLine(g *gocui.Gui, v *gocui.View) error {
	return g.SetCurrentView("cmdline")

}

func keybindings(g *gocui.Gui) error {
	if err := g.SetKeybinding("", gocui.KeyCtrlM, 0, focusMain); err != nil {
		return err
	}
	if err := g.SetKeybinding("", gocui.KeyCtrlS, 0, focusSide); err != nil {
		return err
	}
	if err := g.SetKeybinding("", gocui.KeyCtrlL, 0, focusCmdLine); err != nil {
		return err
	}
	if err := g.SetKeybinding("", gocui.KeyCtrlC, 0, quit); err != nil {
		return err
	}
	if err := g.SetKeybinding("main", 'q', 0, quit); err != nil {
		return err
	}
	return nil
}

func start(g *gocui.Gui) error {
	if err := g.SetCurrentView("main"); err != nil {
		return err
	}
	g.ShowCursor = true
	return nil
}

func quit(g *gocui.Gui, v *gocui.View) error {
	return gocui.ErrorQuit
}

func main() {
	var err error

	g := gocui.NewGui()
	if err := g.Init(); err != nil {
		log.Panicln(err)
	}
	defer g.Close()

	g.Layout = layout
	g.Start = start

	if err := keybindings(g); err != nil {
		log.Panicln(err)
	}

	err = g.MainLoop()
	if err != nil && err != gocui.ErrorQuit {
		log.Panicln(err)
	}
}
<|endoftext|>


<commit_before>package bitbucket

import (
	"context"
	"crypto/sha256"
	"fmt"
	"net/http"
	"path/filepath"
	"strings"
	"sync"

	"github.com/reviewdog/reviewdog"
	"github.com/reviewdog/reviewdog/proto/rdf"
	"github.com/reviewdog/reviewdog/service/bitbucket/openapi"
	"google.golang.org/protobuf/proto"
)

var _ reviewdog.CommentService = &ReportAnnotator{}

const (
	// avatar from https://github.com/apps/reviewdog
	logoURL  = "https://avatars1.githubusercontent.com/in/12131"
	reporter = "reviewdog"
	// max amount of annotations in one batch call
	annotationsBatchSize = 100
)

// ReportAnnotator is a comment service for Bitbucket Code Insights reports.
//
// API:
//  https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Bworkspace%7D/%7Brepo_slug%7D/commit/%7Bcommit%7D/reports/%7BreportId%7D/annotations#post
//  POST /2.0/repositories/{username}/{repo_slug}/commit/{commit}/reports/{reportId}/annotations
type ReportAnnotator struct {
	cli         *openapi.APIClient
	sha         string
	owner, repo string

	muAnnotations sync.Mutex
	// store annotations in map per tool name
	// so we can create report per tool
	annotations map[string][]openapi.ReportAnnotation
	severityMap map[rdf.Severity]string

	// wd is working directory relative to root of repository.
	wd         string
	duplicates map[string]struct{}
}

// NewReportAnnotator creates new Bitbucket Report Annotator
func NewReportAnnotator(cli *openapi.APIClient, owner, repo, sha string, runners []string) *ReportAnnotator {
	r := &ReportAnnotator{
		cli:         cli,
		sha:         sha,
		owner:       owner,
		repo:        repo,
		annotations: make(map[string][]openapi.ReportAnnotation, len(runners)),
		severityMap: map[rdf.Severity]string{
			rdf.Severity_INFO:    annotationSeverityLow,
			rdf.Severity_WARNING: annotationSeverityMedium,
			rdf.Severity_ERROR:   annotationSeverityHigh,
		},
		duplicates: map[string]struct{}{},
	}

	// pre populate map of annotations, so we still create passed (green) report
	// if no issues found from the specific tool
	for _, runner := range runners {
		if len(runner) == 0 {
			continue
		}
		r.annotations[runner] = []openapi.ReportAnnotation{}
		// create Pending report for each tool
		_ = r.createOrUpdateReport(context.Background(), reportID(runner, reporter), reportTitle(runner, reporter), reportResultPending)
	}

	return r
}

// Post accepts a comment and holds it. Flush method actually posts comments to
// Bitbucket in batch.
func (r *ReportAnnotator) Post(_ context.Context, c *reviewdog.Comment) error {
	c.Result.Diagnostic.GetLocation().Path = filepath.ToSlash(
		filepath.Join(r.wd, c.Result.Diagnostic.GetLocation().GetPath()))
	r.muAnnotations.Lock()
	defer r.muAnnotations.Unlock()

	anot := r.annotationFromReviewDogComment(*c)

	// deduplicate event, because some reporters might report
	// it twice, and bitbucket api will complain on duplicated
	// external id of annotation
	if _, ok := r.duplicates[*anot.ExternalId]; !ok {
		r.annotations[c.ToolName] = append(r.annotations[c.ToolName], anot)
	}

	r.duplicates[*anot.ExternalId] = struct{}{}
	return nil
}

// Flush posts comments which has not been posted yet.
func (r *ReportAnnotator) Flush(ctx context.Context) error {
	r.muAnnotations.Lock()
	defer r.muAnnotations.Unlock()

	// create/update/annotate report per tool
	for tool, annotations := range r.annotations {
		reportID := reportID(reporter, tool)
		title := reportTitle(tool, reporter)
		if len(annotations) == 0 {
			// if no annotation, create Passed report
			if err := r.createOrUpdateReport(ctx, reportID, title, reportResultPassed); err != nil {
				return err
			}
			// and move one
			continue
		}

		// create report or update report first, with the failed status
		if err := r.createOrUpdateReport(ctx, reportID, title, reportResultFailed); err != nil {
			return err
		}

		// send annotations in batches, because of the api max payload size limit
		for start, annCount := 0, len(annotations); start < annCount; start += annotationsBatchSize {
			end := start + annotationsBatchSize

			if end > annCount {
				end = annCount
			}

			// add annotations to the report
			_, resp, err := r.cli.ReportsApi.BulkCreateOrUpdateAnnotations(
				ctx, r.owner, r.repo, r.sha, reportID,
			).Body(annotations[start:end]).Execute()

			if err := checkAPIError(err, resp, http.StatusOK); err != nil {
				return fmt.Errorf("bitbucket.BulkCreateOrUpdateAnnotations: %s", err)
			}
		}
	}

	return nil
}

func (r *ReportAnnotator) annotationFromReviewDogComment(c reviewdog.Comment) openapi.ReportAnnotation {
	a := openapi.NewReportAnnotation()

	// TODO: allow providing different annotation types in future
	a.SetAnnotationType(annotationTypeCodeSmell)
	// hash the output of linter and use it as external id
	a.SetExternalId(externalIDFromDiagnostic(c.Result.Diagnostic))
	a.SetSummary(c.Result.Diagnostic.GetMessage())
	a.SetDetails(fmt.Sprintf(`[%s] %s`, c.ToolName, c.Result.Diagnostic.GetMessage()))
	a.SetLine(c.Result.Diagnostic.GetLocation().GetRange().GetStart().GetLine())
	a.SetPath(c.Result.Diagnostic.GetLocation().GetPath())
	if v, ok := r.severityMap[c.Result.Diagnostic.GetSeverity()]; ok {
		a.SetSeverity(v)
	}
	if link := c.Result.Diagnostic.GetCode().GetUrl(); link != "" {
		a.SetLink(link)
	}

	return *a
}

func (r *ReportAnnotator) createOrUpdateReport(ctx context.Context, redportID, title, reportStatus string) error {
	var report = openapi.NewReport()
	report.SetTitle(title)
	// TODO: different report types?
	report.SetReportType(reportTypeBug)
	report.SetReporter(reporter)
	report.SetLogoUrl(logoURL)
	report.SetResult(reportStatus)
	if reportStatus == reportResultPassed {
		report.SetDetails("Great news! Reviewdog couldn't spot any issues!")
	} else {
		report.SetDetails("Woof-Woof! This report generated for you by reviewdog")
	}

	_, resp, err := r.cli.ReportsApi.CreateOrUpdateReport(
		ctx, r.owner, r.repo, r.sha, redportID,
	).Body(*report).Execute()

	if err := checkAPIError(err, resp, http.StatusOK); err != nil {
		return fmt.Errorf("bitbucket.CreateOrUpdateReport: %s", err)
	}

	return nil
}

func hash(b []byte) string {
	h := sha256.New()
	_, _ = h.Write(b)
	return fmt.Sprintf("%x", h.Sum(nil))
}

// Note that it might be good to create external ID from the diagnostic
// content along with *original line* (by using git blame for example) to
// generate unique ID, but it hashes the Diagnostic message for simplicity.
func externalIDFromDiagnostic(d *rdf.Diagnostic) string {
	b, err := proto.Marshal(d)
	if err != nil {
		b = []byte(d.OriginalOutput)
	}
	return hash(b)
}

func reportID(ids ...string) string {
	return strings.ReplaceAll(strings.ToLower(strings.Join(ids, "-")), " ", "_")
}

func reportTitle(tool, reporter string) string {
	return fmt.Sprintf("[%s] %s report", tool, reporter)
}
<commit_msg>add different message for pending report type<commit_after>package bitbucket

import (
	"context"
	"crypto/sha256"
	"fmt"
	"net/http"
	"path/filepath"
	"strings"
	"sync"

	"github.com/reviewdog/reviewdog"
	"github.com/reviewdog/reviewdog/proto/rdf"
	"github.com/reviewdog/reviewdog/service/bitbucket/openapi"
	"google.golang.org/protobuf/proto"
)

var _ reviewdog.CommentService = &ReportAnnotator{}

const (
	// avatar from https://github.com/apps/reviewdog
	logoURL  = "https://avatars1.githubusercontent.com/in/12131"
	reporter = "reviewdog"
	// max amount of annotations in one batch call
	annotationsBatchSize = 100
)

// ReportAnnotator is a comment service for Bitbucket Code Insights reports.
//
// API:
//  https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Bworkspace%7D/%7Brepo_slug%7D/commit/%7Bcommit%7D/reports/%7BreportId%7D/annotations#post
//  POST /2.0/repositories/{username}/{repo_slug}/commit/{commit}/reports/{reportId}/annotations
type ReportAnnotator struct {
	cli         *openapi.APIClient
	sha         string
	owner, repo string

	muAnnotations sync.Mutex
	// store annotations in map per tool name
	// so we can create report per tool
	annotations map[string][]openapi.ReportAnnotation
	severityMap map[rdf.Severity]string

	// wd is working directory relative to root of repository.
	wd         string
	duplicates map[string]struct{}
}

// NewReportAnnotator creates new Bitbucket Report Annotator
func NewReportAnnotator(cli *openapi.APIClient, owner, repo, sha string, runners []string) *ReportAnnotator {
	r := &ReportAnnotator{
		cli:         cli,
		sha:         sha,
		owner:       owner,
		repo:        repo,
		annotations: make(map[string][]openapi.ReportAnnotation, len(runners)),
		severityMap: map[rdf.Severity]string{
			rdf.Severity_INFO:    annotationSeverityLow,
			rdf.Severity_WARNING: annotationSeverityMedium,
			rdf.Severity_ERROR:   annotationSeverityHigh,
		},
		duplicates: map[string]struct{}{},
	}

	// pre populate map of annotations, so we still create passed (green) report
	// if no issues found from the specific tool
	for _, runner := range runners {
		if len(runner) == 0 {
			continue
		}
		r.annotations[runner] = []openapi.ReportAnnotation{}
		// create Pending report for each tool
		_ = r.createOrUpdateReport(context.Background(), reportID(runner, reporter), reportTitle(runner, reporter), reportResultPending)
	}

	return r
}

// Post accepts a comment and holds it. Flush method actually posts comments to
// Bitbucket in batch.
func (r *ReportAnnotator) Post(_ context.Context, c *reviewdog.Comment) error {
	c.Result.Diagnostic.GetLocation().Path = filepath.ToSlash(
		filepath.Join(r.wd, c.Result.Diagnostic.GetLocation().GetPath()))
	r.muAnnotations.Lock()
	defer r.muAnnotations.Unlock()

	anot := r.annotationFromReviewDogComment(*c)

	// deduplicate event, because some reporters might report
	// it twice, and bitbucket api will complain on duplicated
	// external id of annotation
	if _, ok := r.duplicates[*anot.ExternalId]; !ok {
		r.annotations[c.ToolName] = append(r.annotations[c.ToolName], anot)
	}

	r.duplicates[*anot.ExternalId] = struct{}{}
	return nil
}

// Flush posts comments which has not been posted yet.
func (r *ReportAnnotator) Flush(ctx context.Context) error {
	r.muAnnotations.Lock()
	defer r.muAnnotations.Unlock()

	// create/update/annotate report per tool
	for tool, annotations := range r.annotations {
		reportID := reportID(reporter, tool)
		title := reportTitle(tool, reporter)
		if len(annotations) == 0 {
			// if no annotation, create Passed report
			if err := r.createOrUpdateReport(ctx, reportID, title, reportResultPassed); err != nil {
				return err
			}
			// and move one
			continue
		}

		// create report or update report first, with the failed status
		if err := r.createOrUpdateReport(ctx, reportID, title, reportResultFailed); err != nil {
			return err
		}

		// send annotations in batches, because of the api max payload size limit
		for start, annCount := 0, len(annotations); start < annCount; start += annotationsBatchSize {
			end := start + annotationsBatchSize

			if end > annCount {
				end = annCount
			}

			// add annotations to the report
			_, resp, err := r.cli.ReportsApi.BulkCreateOrUpdateAnnotations(
				ctx, r.owner, r.repo, r.sha, reportID,
			).Body(annotations[start:end]).Execute()

			if err := checkAPIError(err, resp, http.StatusOK); err != nil {
				return fmt.Errorf("bitbucket.BulkCreateOrUpdateAnnotations: %s", err)
			}
		}
	}

	return nil
}

func (r *ReportAnnotator) annotationFromReviewDogComment(c reviewdog.Comment) openapi.ReportAnnotation {
	a := openapi.NewReportAnnotation()

	// TODO: allow providing different annotation types in future
	a.SetAnnotationType(annotationTypeCodeSmell)
	// hash the output of linter and use it as external id
	a.SetExternalId(externalIDFromDiagnostic(c.Result.Diagnostic))
	a.SetSummary(c.Result.Diagnostic.GetMessage())
	a.SetDetails(fmt.Sprintf(`[%s] %s`, c.ToolName, c.Result.Diagnostic.GetMessage()))
	a.SetLine(c.Result.Diagnostic.GetLocation().GetRange().GetStart().GetLine())
	a.SetPath(c.Result.Diagnostic.GetLocation().GetPath())
	if v, ok := r.severityMap[c.Result.Diagnostic.GetSeverity()]; ok {
		a.SetSeverity(v)
	}
	if link := c.Result.Diagnostic.GetCode().GetUrl(); link != "" {
		a.SetLink(link)
	}

	return *a
}

func (r *ReportAnnotator) createOrUpdateReport(ctx context.Context, redportID, title, reportStatus string) error {
	var report = openapi.NewReport()
	report.SetTitle(title)
	// TODO: different report types?
	report.SetReportType(reportTypeBug)
	report.SetReporter(reporter)
	report.SetLogoUrl(logoURL)
	report.SetResult(reportStatus)
	switch reportStatus {
	case reportResultPassed:
		report.SetDetails("Great news! Reviewdog couldn't spot any issues!")
	case reportResultPending:
		report.SetDetails("Please wait for Reviewdog to finish checking your code for issues.")
	default:
		report.SetDetails("Woof-Woof! This report generated for you by reviewdog.")
	}

	_, resp, err := r.cli.ReportsApi.CreateOrUpdateReport(
		ctx, r.owner, r.repo, r.sha, redportID,
	).Body(*report).Execute()

	if err := checkAPIError(err, resp, http.StatusOK); err != nil {
		return fmt.Errorf("bitbucket.CreateOrUpdateReport: %s", err)
	}

	return nil
}

func hash(b []byte) string {
	h := sha256.New()
	_, _ = h.Write(b)
	return fmt.Sprintf("%x", h.Sum(nil))
}

// Note that it might be good to create external ID from the diagnostic
// content along with *original line* (by using git blame for example) to
// generate unique ID, but it hashes the Diagnostic message for simplicity.
func externalIDFromDiagnostic(d *rdf.Diagnostic) string {
	b, err := proto.Marshal(d)
	if err != nil {
		b = []byte(d.OriginalOutput)
	}
	return hash(b)
}

func reportID(ids ...string) string {
	return strings.ReplaceAll(strings.ToLower(strings.Join(ids, "-")), " ", "_")
}

func reportTitle(tool, reporter string) string {
	return fmt.Sprintf("[%s] %s report", tool, reporter)
}
<|endoftext|>


<commit_before>package jsonapi

import (
	"encoding/json"
	"errors"
	"fmt"
	"reflect"
	"strings"
	"time"
)

// UnmarshalIdentifier interface to set ID when unmarshalling
type UnmarshalIdentifier interface {
	SetID(string) error
}

// UnmarshalToOneRelations must be implemented to unmarshal to-one relations
type UnmarshalToOneRelations interface {
	SetToOneReferenceID(name, ID string) error
}

// UnmarshalToManyRelations must be implemented to unmarshal to-many relations
type UnmarshalToManyRelations interface {
	SetToManyReferenceIDs(name string, IDs []string) error
}

// The EditToManyRelations interface can be optionally implemented to add and delete to-many
// relationships on a already unmarshalled struct. These methods are used by our API for the to-many
// relationship update routes.
/*
There are 3 HTTP Methods to edit to-many relations:

	PATCH /v1/posts/1/comments
	Content-Type: application/vnd.api+json
	Accept: application/vnd.api+json

	{
	  "data": [
		{ "type": "comments", "id": "2" },
		{ "type": "comments", "id": "3" }
	  ]
	}

this replaces all of the comments that belong to post with ID 1 and the SetToManyReferenceIDs method
will be called

	POST /v1/posts/1/comments
	Content-Type: application/vnd.api+json
	Accept: application/vnd.api+json

	{
	  "data": [
		{ "type": "comments", "id": "123" }
	  ]
	}

adds a new comment to the post with ID 1. The AddToManyIDs methid will be called.

	DELETE /v1/posts/1/comments
	Content-Type: application/vnd.api+json
	Accept: application/vnd.api+json

	{
	  "data": [
		{ "type": "comments", "id": "12" },
		{ "type": "comments", "id": "13" }
	  ]
	}

deletes comments that belong to post with ID 1. The DeleteToManyIDs method will be called.
*/
type EditToManyRelations interface {
	AddToManyIDs(name string, IDs []string) error
	DeleteToManyIDs(name string, IDs []string) error
}

var (
	errInterface         = errors.New("target must implement UnmarshalIdentifier interface")
	errAttributesMissing = errors.New("missing mandatory attributes object")
)

// Unmarshal reads a jsonapi compatible JSON as []byte
// target must at least implement the `UnmarshalIdentifier` interface.
func Unmarshal(data []byte, target interface{}) error {
	ctx := &Document{}
	err := json.Unmarshal(data, ctx)
	if err != nil {
		return err
	}

	if ctx.Data == nil {
		return errors.New(`Source JSON is empty and has no "atributes" payload object`)
	}

	if ctx.Data.DataObject != nil {
		return setDataIntoTarget(ctx.Data.DataObject, target)
	}

	if ctx.Data.DataArray != nil {
		if reflect.TypeOf(target).Elem().Kind() != reflect.Slice {
			return errors.New("Source JSON contained an array, but single record was expected")
		}
		targetType := reflect.TypeOf(target).Elem().Elem()
		targetPointer := reflect.ValueOf(target)
		targetValue := targetPointer.Elem()

		for _, record := range ctx.Data.DataArray {
			// check if there already is an entry with the same id in target slice, otherwise
			// create a new target and append
			var targetRecord, emptyValue reflect.Value
			for i := 0; i < targetValue.Len(); i++ {
				marshalCasted, ok := targetValue.Index(i).Interface().(MarshalIdentifier)
				if !ok {
					return errors.New("existing structs must implement interface MarshalIdentifier")
				}
				if record.ID == marshalCasted.GetID() {
					targetRecord = targetValue.Index(i).Addr()
					break
				}
			}

			if targetRecord == emptyValue || targetRecord.IsNil() {
				targetRecord = reflect.New(targetType)
				err := setDataIntoTarget(&record, targetRecord.Interface())
				if err != nil {
					return err
				}
				targetValue = reflect.Append(targetValue, targetRecord.Elem())
			} else {
				err := setDataIntoTarget(&record, targetRecord.Interface())
				if err != nil {
					return err
				}
			}
		}

		targetPointer.Elem().Set(targetValue)
	}

	return nil
}

func setDataIntoTarget(data *Data, target interface{}) error {
	if reflect.ValueOf(target).Kind() == reflect.Struct {
		target = reflect.ValueOf(target).Addr().Interface()
	}
	castedTarget, ok := target.(UnmarshalIdentifier)
	if !ok {
		return errInterface
	}

	err := checkType(data.Type, castedTarget)
	if err != nil {
		return err
	}

	if data.Attributes == nil {
		return errAttributesMissing
	}

	err = json.Unmarshal(data.Attributes, castedTarget)
	if err != nil {
		return err
	}
	castedTarget.SetID(data.ID)
	return setRelationshipIDs(data.Relationships, castedTarget)
}

// extracts all found relationships and set's them via SetToOneReferenceID or SetToManyReferenceIDs
func setRelationshipIDs(relationships map[string]Relationship, target UnmarshalIdentifier) error {
	toOneError := fmt.Errorf("struct %s does not implement UnmarshalToOneRelations", reflect.TypeOf(target))
	toManyError := fmt.Errorf("struct %s does not implement UnmarshalToManyRelations", reflect.TypeOf(target))

	for name, rel := range relationships {
		// relationship is empty case
		if rel.Data == nil {
			if Pluralize(name) == name {
				castedToMany, ok := target.(UnmarshalToManyRelations)
				if !ok {
					return toManyError
				}

				castedToMany.SetToManyReferenceIDs(name, []string{})
				break
			}

			castedToOne, ok := target.(UnmarshalToOneRelations)
			if !ok {
				return toOneError
			}

			castedToOne.SetToOneReferenceID(name, "")
			break
		}

		// valid toOne case
		if rel.Data.DataObject != nil {
			castedToOne, ok := target.(UnmarshalToOneRelations)
			if !ok {
				return toOneError
			}
			err := castedToOne.SetToOneReferenceID(name, rel.Data.DataObject.ID)
			if err != nil {
				return err
			}
		}

		// valid toMany case
		if rel.Data.DataArray != nil {
			castedToMany, ok := target.(UnmarshalToManyRelations)
			if !ok {
				return toManyError
			}
			IDs := make([]string, len(rel.Data.DataArray))
			for index, relData := range rel.Data.DataArray {
				IDs[index] = relData.ID
			}
			err := castedToMany.SetToManyReferenceIDs(name, IDs)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func checkType(incomingType string, target UnmarshalIdentifier) error {
	actualType := getStructType(target)
	if incomingType != actualType {
		return fmt.Errorf("Type %s in JSON does not match target struct type %s", incomingType, actualType)
	}

	return nil
}

// UnmarshalFromJSON reads a JSONAPI compatible JSON document to a model struct
// target must be a struct or a slice of it
func UnmarshalFromJSON(data []byte, target interface{}) error {
	var ctx map[string]interface{}
	err := json.Unmarshal(data, &ctx)
	if err != nil {
		return err
	}
	return Unmarshal([]byte("{}"), target)
}

// UnmarshalInto reads input params for one struct from `input` and marshals it into `targetSliceVal`,
// which may be a slice of targetStructType or a slice of pointers to targetStructType.
func UnmarshalInto(input map[string]interface{}, targetStructType reflect.Type, targetSliceVal *reflect.Value) error {
	// Read models slice
	var modelsInterface interface{}

	if modelsInterface = input["data"]; modelsInterface == nil {
		return errors.New("expected root document to include a data key but it didn't")
	}

	models, ok := modelsInterface.([]interface{})
	if !ok {
		models = []interface{}{modelsInterface}
	}

	// Read all the models
	for _, m := range models {
		data, ok := m.(map[string]interface{})
		if !ok {
			return errors.New("expected an array of objects under key data")
		}

		var val reflect.Value
		isNew := true
		id := ""

		if v := data["id"]; v != nil {
			id, ok = v.(string)
			if !ok {
				return errors.New("id must be a string")
			}

			// If we have an ID, check if there's already an object with that ID in the slice
			for i := 0; i < targetSliceVal.Len(); i++ {
				obj := targetSliceVal.Index(i)
				existingObj, ok := obj.Interface().(MarshalIdentifier)
				if !ok {
					return errors.New("existing structs must implement interface MarshalIdentifier")
				}
				otherID := existingObj.GetID()
				if otherID == id {
					if obj.Type().Kind() == reflect.Struct {
						val = obj
					} else {
						val = obj.Elem()
					}
					isNew = false
					break
				}
			}
		}
		// If the struct wasn't already there for updating, make a new one
		if !val.IsValid() {
			val = reflect.New(targetStructType).Elem()
		}

		for k, v := range data {
			switch k {
			case "relationships":
				relationshipsMap, ok := v.(map[string]interface{})
				if !ok {
					return errors.New("expected relationships to be an object")
				}
				if err := unmarshalRelationships(val, relationshipsMap); err != nil {
					return err
				}

			case "id":
				var i reflect.Value
				if val.CanAddr() {
					i = val.Addr()
				}
				targetStruct, ok := i.Interface().(UnmarshalIdentifier)
				if !ok {
					return errors.New("All target structs must implement UnmarshalIdentifier interface")
				}

				// Allow conversion of string id to int
				id, ok = v.(string)
				if !ok {
					return errors.New("expected id to be of type string")
				}

				targetStruct.SetID(id)

			case "type":
				var expectedType string
				structType, ok := v.(string)
				if !ok {
					return errors.New("type must be string")
				}

				entityName, ok := val.Interface().(EntityNamer)
				if ok {
					expectedType = entityName.GetName()
				} else {
					expectedType = Pluralize(Jsonify(targetStructType.Name()))
				}
				if structType != expectedType {
					return fmt.Errorf("type %s does not match expected type %s of target struct", structType, expectedType)
				}
				// do not unmarshal the `type` field

			case "attributes":
				attributes, ok := v.(map[string]interface{})
				if !ok {
					return errors.New("expected attributes to be an object")
				}

				for key, attributeValue := range attributes {
					fieldName := Dejsonify(key)
					field := val.FieldByName(fieldName)
					fieldType, found := val.Type().FieldByName(fieldName)
					if !found {
						//check if there is any field tag with the given name available
						field, found = getFieldByTagName(val, fieldName)
					}

					if !found || fieldType.Tag.Get("jsonapi") == "-" {
						return fmt.Errorf("invalid key \"%s\" in json. Cannot be assigned to target struct \"%s\"", key, targetStructType.Name())
					}

					value := reflect.ValueOf(attributeValue)

					if !field.CanInterface() {
						return fmt.Errorf("field not exported. Expected field with name %s to exist", fieldName)
					}

					if value.IsValid() {

						plainValue := reflect.ValueOf(attributeValue)

						switch field.Interface().(type) {
						case time.Time:
							t, err := time.Parse(time.RFC3339, plainValue.String())
							if err != nil {
								return errors.New("expected RFC3339 time string, got '" + plainValue.String() + "'")
							}

							field.Set(reflect.ValueOf(t))
						default:
							if field.CanAddr() {
								switch field.Addr().Interface().(type) {
								default:
									err := setFieldValue(&field, plainValue)
									if err != nil {
										return fmt.Errorf("Could not set field '%s'. %s", fieldName, err.Error())
									}

								}
							} else {
								err := setFieldValue(&field, plainValue)
								if err != nil {
									return fmt.Errorf("Could not set field '%s'. %s", fieldName, err.Error())
								}
							}
						}
					} else {
						// Set the field to its zero value if its type is from the zero or null package.
						if extType := field.Type(); extType != nil && extType.Kind().String() == "struct" {
							fieldSource := extType.Field(0).Name
							if strings.HasPrefix(fieldSource, "Null") || strings.HasPrefix(fieldSource, "Zero") || strings.HasPrefix(fieldSource, "Time") {
								field.Set(reflect.Zero(field.Type()))
							}

						}
					}

				}
			}
		}

		if isNew {
			if targetSliceVal.Type().Elem().Kind() == reflect.Struct {
				*targetSliceVal = reflect.Append(*targetSliceVal, val)
			} else {
				*targetSliceVal = reflect.Append(*targetSliceVal, val.Addr())
			}
		}
	}

	return nil
}

// check if there is any field tag with the given name available
func getFieldByTagName(val reflect.Value, fieldName string) (field reflect.Value, found bool) {
	for x := 0; x < val.NumField(); x++ {
		tfield := val.Type().Field(x)
		if tfield.Tag.Get("jsonapi") == "-" {
			continue
		}

		// check if there is an embedded struct which needs to be searched
		if val.Field(x).CanAddr() && val.Field(x).Addr().CanInterface() {
			_, isEmbedded := val.Field(x).Addr().Interface().(UnmarshalIdentifier)
			if isEmbedded {
				field, found = getFieldByTagName(val.Field(x), fieldName)
				if found {
					return
				}
			}
		}

		// try to find the field
		name := GetTagValueByName(tfield, "name")
		if strings.ToLower(name) == strings.ToLower(fieldName) {
			field = val.Field(x)
			found = true
			return
		}
	}

	return
}

// setFieldValue in a json object, there is only the number type, which defaults to float64. This method convertes float64 to the value
// of the underlying struct field, for example uint64, or int32 etc...
// If the field type is not one of the integers, it just sets the value
func setFieldValue(field *reflect.Value, value reflect.Value) (err error) {
	// catch all invalid types and return an error
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("Value '%v' had wrong type", value.Interface())
		}
	}()

	// check for Unmarshaler interface first, after that try to guess the right type
	target, ok := field.Addr().Interface().(json.Unmarshaler)
	if ok {
		marshaledValue, err := json.Marshal(value.Interface())
		if err != nil {
			return err
		}

		err = target.UnmarshalJSON(marshaledValue)
		if err != nil {
			return err
		}
	} else {
		switch field.Type().Kind() {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			field.SetInt(int64(value.Float()))
		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
			field.SetUint(uint64(value.Float()))
		case reflect.Slice:
			// we always get a []interface{] from json and now need to cast to the right slice type
			if value.Type() == reflect.TypeOf([]interface{}{}) {
				targetSlice := reflect.MakeSlice(field.Type(), 0, value.Len())
				sliceData := value.Interface().([]interface{})

				// only iterate over the array if it's not empty
				if value.Len() > 0 {
					targetType := reflect.TypeOf(sliceData[0])
					for _, entry := range sliceData {
						casted := reflect.ValueOf(entry).Convert(targetType)
						targetSlice = reflect.Append(targetSlice, casted)
					}
				}

				field.Set(targetSlice)
			} else {
				// we have the correct type, hm this is only for tests that use direct type at the moment.. we have to refactor the unmarshalling
				// anyways..
				field.Set(value)
			}
		default:
			field.Set(value)
		}
	}

	return nil
}

// UnmarshalRelationshipsData is used by api2go.API to only unmarshal references inside a data object.
// The target interface must implement UnmarshalToOneRelations or UnmarshalToManyRelations interface.
// The linksMap is the content of the data object from the json
func UnmarshalRelationshipsData(target interface{}, name string, links interface{}) error {
	return processRelationshipsData(links, name, target)
}

func unmarshalRelationships(val reflect.Value, relationshipsMap map[string]interface{}) error {
	for relationshipName, relationships := range relationshipsMap {
		relationships, ok := relationships.(map[string]interface{})
		if !ok {
			return fmt.Errorf("link field for %s has invalid format, must be map[string]interface{}", relationshipName)
		}
		_, ok = relationships["data"]
		if !ok {
			return fmt.Errorf("Missing data field for %s", relationshipName)
		}

		if val.CanAddr() {
			val = val.Addr()
		}

		err := processRelationshipsData(relationships["data"], relationshipName, val.Interface())
		if err != nil {
			return err
		}
	}

	return nil
}

func processRelationshipsData(data interface{}, linkName string, target interface{}) error {
	hasOne, ok := data.(map[string]interface{})
	if ok {
		hasOneID, ok := hasOne["id"].(string)
		if !ok {
			return fmt.Errorf("data object must have a field id for %s", linkName)
		}

		target, ok := target.(UnmarshalToOneRelations)
		if !ok {
			return errors.New("target struct must implement interface UnmarshalToOneRelations")
		}

		target.SetToOneReferenceID(linkName, hasOneID)
	} else if data == nil {
		// this means that a to-one relationship must be deleted
		target, ok := target.(UnmarshalToOneRelations)
		if !ok {
			return errors.New("target struct must implement interface UnmarshalToOneRelations")
		}

		target.SetToOneReferenceID(linkName, "")
	} else {
		hasMany, ok := data.([]interface{})
		if !ok {
			return fmt.Errorf("invalid data object or array, must be an object with \"id\" and \"type\" field for %s", linkName)
		}

		target, ok := target.(UnmarshalToManyRelations)
		if !ok {
			return errors.New("target struct must implement interface UnmarshalToManyRelations")
		}

		hasManyIDs := []string{}

		for _, entry := range hasMany {
			data, ok := entry.(map[string]interface{})
			if !ok {
				return fmt.Errorf("entry in data array must be an object for %s", linkName)
			}
			dataID, ok := data["id"].(string)
			if !ok {
				return fmt.Errorf("all data objects must have a field id for %s", linkName)
			}

			hasManyIDs = append(hasManyIDs, dataID)
		}

		target.SetToManyReferenceIDs(linkName, hasManyIDs)
	}

	return nil
}
<commit_msg>delete old obsolete code from unmarshal.go<commit_after>package jsonapi

import (
	"encoding/json"
	"errors"
	"fmt"
	"reflect"
)

// UnmarshalIdentifier interface to set ID when unmarshalling
type UnmarshalIdentifier interface {
	SetID(string) error
}

// UnmarshalToOneRelations must be implemented to unmarshal to-one relations
type UnmarshalToOneRelations interface {
	SetToOneReferenceID(name, ID string) error
}

// UnmarshalToManyRelations must be implemented to unmarshal to-many relations
type UnmarshalToManyRelations interface {
	SetToManyReferenceIDs(name string, IDs []string) error
}

// The EditToManyRelations interface can be optionally implemented to add and delete to-many
// relationships on a already unmarshalled struct. These methods are used by our API for the to-many
// relationship update routes.
/*
There are 3 HTTP Methods to edit to-many relations:

	PATCH /v1/posts/1/comments
	Content-Type: application/vnd.api+json
	Accept: application/vnd.api+json

	{
	  "data": [
		{ "type": "comments", "id": "2" },
		{ "type": "comments", "id": "3" }
	  ]
	}

this replaces all of the comments that belong to post with ID 1 and the SetToManyReferenceIDs method
will be called

	POST /v1/posts/1/comments
	Content-Type: application/vnd.api+json
	Accept: application/vnd.api+json

	{
	  "data": [
		{ "type": "comments", "id": "123" }
	  ]
	}

adds a new comment to the post with ID 1. The AddToManyIDs methid will be called.

	DELETE /v1/posts/1/comments
	Content-Type: application/vnd.api+json
	Accept: application/vnd.api+json

	{
	  "data": [
		{ "type": "comments", "id": "12" },
		{ "type": "comments", "id": "13" }
	  ]
	}

deletes comments that belong to post with ID 1. The DeleteToManyIDs method will be called.
*/
type EditToManyRelations interface {
	AddToManyIDs(name string, IDs []string) error
	DeleteToManyIDs(name string, IDs []string) error
}

var (
	errInterface         = errors.New("target must implement UnmarshalIdentifier interface")
	errAttributesMissing = errors.New("missing mandatory attributes object")
)

// Unmarshal reads a jsonapi compatible JSON as []byte
// target must at least implement the `UnmarshalIdentifier` interface.
func Unmarshal(data []byte, target interface{}) error {
	ctx := &Document{}
	err := json.Unmarshal(data, ctx)
	if err != nil {
		return err
	}

	if ctx.Data == nil {
		return errors.New(`Source JSON is empty and has no "atributes" payload object`)
	}

	if ctx.Data.DataObject != nil {
		return setDataIntoTarget(ctx.Data.DataObject, target)
	}

	if ctx.Data.DataArray != nil {
		if reflect.TypeOf(target).Elem().Kind() != reflect.Slice {
			return errors.New("Source JSON contained an array, but single record was expected")
		}
		targetType := reflect.TypeOf(target).Elem().Elem()
		targetPointer := reflect.ValueOf(target)
		targetValue := targetPointer.Elem()

		for _, record := range ctx.Data.DataArray {
			// check if there already is an entry with the same id in target slice, otherwise
			// create a new target and append
			var targetRecord, emptyValue reflect.Value
			for i := 0; i < targetValue.Len(); i++ {
				marshalCasted, ok := targetValue.Index(i).Interface().(MarshalIdentifier)
				if !ok {
					return errors.New("existing structs must implement interface MarshalIdentifier")
				}
				if record.ID == marshalCasted.GetID() {
					targetRecord = targetValue.Index(i).Addr()
					break
				}
			}

			if targetRecord == emptyValue || targetRecord.IsNil() {
				targetRecord = reflect.New(targetType)
				err := setDataIntoTarget(&record, targetRecord.Interface())
				if err != nil {
					return err
				}
				targetValue = reflect.Append(targetValue, targetRecord.Elem())
			} else {
				err := setDataIntoTarget(&record, targetRecord.Interface())
				if err != nil {
					return err
				}
			}
		}

		targetPointer.Elem().Set(targetValue)
	}

	return nil
}

func setDataIntoTarget(data *Data, target interface{}) error {
	if reflect.ValueOf(target).Kind() == reflect.Struct {
		target = reflect.ValueOf(target).Addr().Interface()
	}
	castedTarget, ok := target.(UnmarshalIdentifier)
	if !ok {
		return errInterface
	}

	err := checkType(data.Type, castedTarget)
	if err != nil {
		return err
	}

	if data.Attributes == nil {
		return errAttributesMissing
	}

	err = json.Unmarshal(data.Attributes, castedTarget)
	if err != nil {
		return err
	}
	castedTarget.SetID(data.ID)
	return setRelationshipIDs(data.Relationships, castedTarget)
}

// extracts all found relationships and set's them via SetToOneReferenceID or SetToManyReferenceIDs
func setRelationshipIDs(relationships map[string]Relationship, target UnmarshalIdentifier) error {
	toOneError := fmt.Errorf("struct %s does not implement UnmarshalToOneRelations", reflect.TypeOf(target))
	toManyError := fmt.Errorf("struct %s does not implement UnmarshalToManyRelations", reflect.TypeOf(target))

	for name, rel := range relationships {
		// relationship is empty case
		if rel.Data == nil {
			if Pluralize(name) == name {
				castedToMany, ok := target.(UnmarshalToManyRelations)
				if !ok {
					return toManyError
				}

				castedToMany.SetToManyReferenceIDs(name, []string{})
				break
			}

			castedToOne, ok := target.(UnmarshalToOneRelations)
			if !ok {
				return toOneError
			}

			castedToOne.SetToOneReferenceID(name, "")
			break
		}

		// valid toOne case
		if rel.Data.DataObject != nil {
			castedToOne, ok := target.(UnmarshalToOneRelations)
			if !ok {
				return toOneError
			}
			err := castedToOne.SetToOneReferenceID(name, rel.Data.DataObject.ID)
			if err != nil {
				return err
			}
		}

		// valid toMany case
		if rel.Data.DataArray != nil {
			castedToMany, ok := target.(UnmarshalToManyRelations)
			if !ok {
				return toManyError
			}
			IDs := make([]string, len(rel.Data.DataArray))
			for index, relData := range rel.Data.DataArray {
				IDs[index] = relData.ID
			}
			err := castedToMany.SetToManyReferenceIDs(name, IDs)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func checkType(incomingType string, target UnmarshalIdentifier) error {
	actualType := getStructType(target)
	if incomingType != actualType {
		return fmt.Errorf("Type %s in JSON does not match target struct type %s", incomingType, actualType)
	}

	return nil
}

// UnmarshalRelationshipsData is used by api2go.API to only unmarshal references inside a data object.
// The target interface must implement UnmarshalToOneRelations or UnmarshalToManyRelations interface.
// The linksMap is the content of the data object from the json
func UnmarshalRelationshipsData(target interface{}, name string, links interface{}) error {
	return processRelationshipsData(links, name, target)
}

func unmarshalRelationships(val reflect.Value, relationshipsMap map[string]interface{}) error {
	for relationshipName, relationships := range relationshipsMap {
		relationships, ok := relationships.(map[string]interface{})
		if !ok {
			return fmt.Errorf("link field for %s has invalid format, must be map[string]interface{}", relationshipName)
		}
		_, ok = relationships["data"]
		if !ok {
			return fmt.Errorf("Missing data field for %s", relationshipName)
		}

		if val.CanAddr() {
			val = val.Addr()
		}

		err := processRelationshipsData(relationships["data"], relationshipName, val.Interface())
		if err != nil {
			return err
		}
	}

	return nil
}

func processRelationshipsData(data interface{}, linkName string, target interface{}) error {
	hasOne, ok := data.(map[string]interface{})
	if ok {
		hasOneID, ok := hasOne["id"].(string)
		if !ok {
			return fmt.Errorf("data object must have a field id for %s", linkName)
		}

		target, ok := target.(UnmarshalToOneRelations)
		if !ok {
			return errors.New("target struct must implement interface UnmarshalToOneRelations")
		}

		target.SetToOneReferenceID(linkName, hasOneID)
	} else if data == nil {
		// this means that a to-one relationship must be deleted
		target, ok := target.(UnmarshalToOneRelations)
		if !ok {
			return errors.New("target struct must implement interface UnmarshalToOneRelations")
		}

		target.SetToOneReferenceID(linkName, "")
	} else {
		hasMany, ok := data.([]interface{})
		if !ok {
			return fmt.Errorf("invalid data object or array, must be an object with \"id\" and \"type\" field for %s", linkName)
		}

		target, ok := target.(UnmarshalToManyRelations)
		if !ok {
			return errors.New("target struct must implement interface UnmarshalToManyRelations")
		}

		hasManyIDs := []string{}

		for _, entry := range hasMany {
			data, ok := entry.(map[string]interface{})
			if !ok {
				return fmt.Errorf("entry in data array must be an object for %s", linkName)
			}
			dataID, ok := data["id"].(string)
			if !ok {
				return fmt.Errorf("all data objects must have a field id for %s", linkName)
			}

			hasManyIDs = append(hasManyIDs, dataID)
		}

		target.SetToManyReferenceIDs(linkName, hasManyIDs)
	}

	return nil
}
<|endoftext|>


<commit_before>package user

import (
	"bytes"
	"encoding/json"
	htmlTemplate "html/template"
	"net/http"
	"net/url"
	"xorkevin.dev/governor"
	"xorkevin.dev/governor/service/user/approval/model"
	"xorkevin.dev/governor/util/rank"
)

type (
	emailNewUser struct {
		Userid    string
		Key       string
		URL       string
		FirstName string
		LastName  string
		Username  string
	}

	queryEmailNewUser struct {
		Userid    string
		Key       string
		FirstName string
		LastName  string
		Username  string
	}
)

const (
	newUserTemplate = "newuser"
)

func (e *emailNewUser) Query() queryEmailNewUser {
	return queryEmailNewUser{
		Userid:    url.QueryEscape(e.Userid),
		Key:       url.QueryEscape(e.Key),
		FirstName: url.QueryEscape(e.FirstName),
		LastName:  url.QueryEscape(e.LastName),
		Username:  url.QueryEscape(e.Username),
	}
}

func (e *emailNewUser) computeURL(base string, tpl *htmlTemplate.Template) error {
	b := &bytes.Buffer{}
	if err := tpl.Execute(b, e.Query()); err != nil {
		return governor.NewError("Failed executing new user url template", http.StatusInternalServerError, err)
	}
	e.URL = base + b.String()
	return nil
}

type (
	resUserUpdate struct {
		Userid   string `json:"userid"`
		Username string `json:"username"`
	}
)

// CreateUser creates a new user and places it into approvals
func (s *service) CreateUser(ruser reqUserPost) (*resUserUpdate, error) {
	m2, err := s.users.GetByUsername(ruser.Username)
	if err != nil && governor.ErrorStatus(err) != http.StatusNotFound {
		return nil, err
	}
	if m2 != nil && m2.Username == ruser.Username {
		return nil, governor.NewErrorUser("Username is already taken", http.StatusBadRequest, nil)
	}

	m2, err = s.users.GetByEmail(ruser.Email)
	if err != nil && governor.ErrorStatus(err) != http.StatusNotFound {
		return nil, err
	}
	if m2 != nil && m2.Email == ruser.Email {
		return nil, governor.NewErrorUser("Email is already used by another account", http.StatusBadRequest, nil)
	}

	m, err := s.users.New(ruser.Username, ruser.Password, ruser.Email, ruser.FirstName, ruser.LastName)
	if err != nil {
		return nil, err
	}

	am := s.approvals.New(m)
	if s.userApproval {
		if err := s.approvals.Insert(am); err != nil {
			if governor.ErrorStatus(err) == http.StatusBadRequest {
				return nil, governor.NewErrorUser("", 0, err)
			}
			return nil, err
		}
	} else {
		code, err := s.approvals.RehashCode(am)
		if err != nil {
			return nil, err
		}
		if err := s.approvals.Insert(am); err != nil {
			if governor.ErrorStatus(err) == http.StatusBadRequest {
				return nil, governor.NewErrorUser("", 0, err)
			}
			return nil, err
		}
		if err := s.sendNewUserEmail(code, am); err != nil {
			return nil, err
		}
	}

	return &resUserUpdate{
		Userid:   m.Userid,
		Username: m.Username,
	}, nil
}

type (
	resApproval struct {
		Userid       string `json:"userid"`
		Username     string `json:"username"`
		Email        string `json:"email"`
		FirstName    string `json:"first_name"`
		LastName     string `json:"last_name"`
		CreationTime int64  `json:"creation_time"`
		Approved     bool   `json:"approved"`
		CodeTime     int64  `json:"code_time"`
	}

	resApprovals struct {
		Approvals []resApproval `json:"approvals"`
	}
)

func (s *service) GetUserApprovals(limit, offset int) (*resApprovals, error) {
	m, err := s.approvals.GetGroup(limit, offset)
	if err != nil {
		return nil, err
	}
	approvals := make([]resApproval, 0, len(m))
	for _, i := range m {
		approvals = append(approvals, resApproval{
			Userid:       i.Userid,
			Username:     i.Username,
			Email:        i.Email,
			FirstName:    i.FirstName,
			LastName:     i.LastName,
			CreationTime: i.CreationTime,
			Approved:     i.Approved,
			CodeTime:     i.CodeTime,
		})
	}
	return &resApprovals{
		Approvals: approvals,
	}, nil
}

func (s *service) ApproveUser(userid string) error {
	m, err := s.approvals.GetByID(userid)
	if err != nil {
		if governor.ErrorStatus(err) == http.StatusNotFound {
			return governor.NewErrorUser("", 0, err)
		}
		return err
	}
	code, err := s.approvals.RehashCode(m)
	if err != nil {
		return err
	}
	if err := s.approvals.Update(m); err != nil {
		return err
	}
	if err := s.sendNewUserEmail(code, m); err != nil {
		return err
	}
	return nil
}

func (s *service) DeleteUserApproval(userid string) error {
	m, err := s.approvals.GetByID(userid)
	if err != nil {
		if governor.ErrorStatus(err) == http.StatusNotFound {
			return governor.NewErrorUser("", 0, err)
		}
		return err
	}
	if err := s.approvals.Delete(m); err != nil {
		return err
	}
	return nil
}

func (s *service) sendNewUserEmail(code string, m *approvalmodel.Model) error {
	emdata := emailNewUser{
		Userid:    m.Userid,
		Key:       code,
		FirstName: m.FirstName,
		LastName:  m.LastName,
		Username:  m.Username,
	}
	if err := emdata.computeURL(s.emailurlbase, s.tplnewuser); err != nil {
		return err
	}
	if err := s.mailer.Send("", "", []string{m.Email}, newUserTemplate, emdata); err != nil {
		return governor.NewError("Failed to send account verification email", http.StatusInternalServerError, err)
	}
	return nil
}

// CommitUser takes a user from approvals and places it into the user db
func (s *service) CommitUser(userid string, key string) (*resUserUpdate, error) {
	am, err := s.approvals.GetByID(userid)
	if err != nil {
		if governor.ErrorStatus(err) == http.StatusNotFound {
			return nil, governor.NewErrorUser("", 0, err)
		}
		return nil, err
	}
	if ok, err := s.approvals.ValidateCode(key, am); err != nil {
		return nil, governor.NewError("Failed to verify key", http.StatusInternalServerError, err)
	} else if !ok {
		return nil, governor.NewErrorUser("Invalid key", http.StatusForbidden, nil)
	}
	m := s.approvals.ToUserModel(am)

	userProps := NewUserProps{
		Userid:       m.Userid,
		Username:     m.Username,
		Email:        m.Email,
		FirstName:    m.FirstName,
		LastName:     m.LastName,
		CreationTime: m.CreationTime,
	}

	b := bytes.Buffer{}
	if err := json.NewEncoder(&b).Encode(userProps); err != nil {
		return nil, governor.NewError("Failed to encode user props to json", http.StatusInternalServerError, err)
	}

	if err := s.users.Insert(m); err != nil {
		if governor.ErrorStatus(err) == http.StatusBadRequest {
			return nil, governor.NewErrorUser("", 0, err)
		}
		return nil, err
	}
	if err := s.roles.InsertRoles(m.Userid, rank.BaseUser()); err != nil {
		return nil, err
	}

	if err := s.queue.Publish(NewUserQueueID, b.Bytes()); err != nil {
		s.logger.Error("Failed to publish new user", map[string]string{
			"error":      err.Error(),
			"actiontype": "publishnewuser",
		})
	}

	if err := s.approvals.Delete(am); err != nil {
		s.logger.Error("Failed to clean up user approval", map[string]string{
			"error":      err.Error(),
			"actiontype": "commitusercleanup",
		})
	}

	s.logger.Info("created user", map[string]string{
		"userid":     m.Userid,
		"username":   m.Username,
		"actiontype": "commituser",
	})

	return &resUserUpdate{
		Userid:   m.Userid,
		Username: m.Username,
	}, nil
}

func (s *service) DeleteUser(userid string, username string, password string) error {
	m, err := s.users.GetByID(userid)
	if err != nil {
		if governor.ErrorStatus(err) == http.StatusNotFound {
			return governor.NewErrorUser("", 0, err)
		}
		return err
	}

	if m.Username != username {
		return governor.NewErrorUser("Information does not match", http.StatusBadRequest, err)
	}
	if roles, err := s.roles.IntersectRoles(userid, rank.Rank{"admin": struct{}{}}); err != nil {
		return err
	} else if roles.Has("admin") {
		return governor.NewErrorUser("Not allowed to delete admin user", http.StatusForbidden, err)
	}
	if ok, err := s.users.ValidatePass(password, m); err != nil {
		return err
	} else if !ok {
		return governor.NewErrorUser("Incorrect password", http.StatusForbidden, nil)
	}

	userProps := DeleteUserProps{
		Userid: m.Userid,
	}
	b := bytes.Buffer{}
	if err := json.NewEncoder(&b).Encode(userProps); err != nil {
		return governor.NewError("Failed to encode user props to json", http.StatusInternalServerError, err)
	}

	if err := s.DeleteUserApikeys(userid); err != nil {
		return err
	}

	if err := s.KillAllSessions(userid); err != nil {
		return err
	}

	if err := s.roles.DeleteAllRoles(userid); err != nil {
		return err
	}

	if err := s.users.Delete(m); err != nil {
		return err
	}

	if err := s.queue.Publish(DeleteUserQueueID, b.Bytes()); err != nil {
		s.logger.Error("failed to publish delete user", map[string]string{
			"error":      err.Error(),
			"actiontype": "publishdeleteuser",
		})
	}
	return nil
}

// DecodeNewUserProps marshals json encoded new user props into a struct
func DecodeNewUserProps(msgdata []byte) (*NewUserProps, error) {
	m := &NewUserProps{}
	if err := json.NewDecoder(bytes.NewBuffer(msgdata)).Decode(m); err != nil {
		return nil, governor.NewError("Failed to decode new user props", http.StatusInternalServerError, err)
	}
	return m, nil
}

// DecodeDeleteUserProps marshals json encoded delete user props into a struct
func DecodeDeleteUserProps(msgdata []byte) (*DeleteUserProps, error) {
	m := &DeleteUserProps{}
	if err := json.NewDecoder(bytes.NewBuffer(msgdata)).Decode(m); err != nil {
		return nil, governor.NewError("Failed to decode delete user props", http.StatusInternalServerError, err)
	}
	return m, nil
}
<commit_msg>Fix commit new user approval check<commit_after>package user

import (
	"bytes"
	"encoding/json"
	htmlTemplate "html/template"
	"net/http"
	"net/url"
	"xorkevin.dev/governor"
	"xorkevin.dev/governor/service/user/approval/model"
	"xorkevin.dev/governor/util/rank"
)

type (
	emailNewUser struct {
		Userid    string
		Key       string
		URL       string
		FirstName string
		LastName  string
		Username  string
	}

	queryEmailNewUser struct {
		Userid    string
		Key       string
		FirstName string
		LastName  string
		Username  string
	}
)

const (
	newUserTemplate = "newuser"
)

func (e *emailNewUser) Query() queryEmailNewUser {
	return queryEmailNewUser{
		Userid:    url.QueryEscape(e.Userid),
		Key:       url.QueryEscape(e.Key),
		FirstName: url.QueryEscape(e.FirstName),
		LastName:  url.QueryEscape(e.LastName),
		Username:  url.QueryEscape(e.Username),
	}
}

func (e *emailNewUser) computeURL(base string, tpl *htmlTemplate.Template) error {
	b := &bytes.Buffer{}
	if err := tpl.Execute(b, e.Query()); err != nil {
		return governor.NewError("Failed executing new user url template", http.StatusInternalServerError, err)
	}
	e.URL = base + b.String()
	return nil
}

type (
	resUserUpdate struct {
		Userid   string `json:"userid"`
		Username string `json:"username"`
	}
)

// CreateUser creates a new user and places it into approvals
func (s *service) CreateUser(ruser reqUserPost) (*resUserUpdate, error) {
	m2, err := s.users.GetByUsername(ruser.Username)
	if err != nil && governor.ErrorStatus(err) != http.StatusNotFound {
		return nil, err
	}
	if m2 != nil && m2.Username == ruser.Username {
		return nil, governor.NewErrorUser("Username is already taken", http.StatusBadRequest, nil)
	}

	m2, err = s.users.GetByEmail(ruser.Email)
	if err != nil && governor.ErrorStatus(err) != http.StatusNotFound {
		return nil, err
	}
	if m2 != nil && m2.Email == ruser.Email {
		return nil, governor.NewErrorUser("Email is already used by another account", http.StatusBadRequest, nil)
	}

	m, err := s.users.New(ruser.Username, ruser.Password, ruser.Email, ruser.FirstName, ruser.LastName)
	if err != nil {
		return nil, err
	}

	am := s.approvals.New(m)
	if s.userApproval {
		if err := s.approvals.Insert(am); err != nil {
			if governor.ErrorStatus(err) == http.StatusBadRequest {
				return nil, governor.NewErrorUser("", 0, err)
			}
			return nil, err
		}
	} else {
		code, err := s.approvals.RehashCode(am)
		if err != nil {
			return nil, err
		}
		if err := s.approvals.Insert(am); err != nil {
			if governor.ErrorStatus(err) == http.StatusBadRequest {
				return nil, governor.NewErrorUser("", 0, err)
			}
			return nil, err
		}
		if err := s.sendNewUserEmail(code, am); err != nil {
			return nil, err
		}
	}

	return &resUserUpdate{
		Userid:   m.Userid,
		Username: m.Username,
	}, nil
}

type (
	resApproval struct {
		Userid       string `json:"userid"`
		Username     string `json:"username"`
		Email        string `json:"email"`
		FirstName    string `json:"first_name"`
		LastName     string `json:"last_name"`
		CreationTime int64  `json:"creation_time"`
		Approved     bool   `json:"approved"`
		CodeTime     int64  `json:"code_time"`
	}

	resApprovals struct {
		Approvals []resApproval `json:"approvals"`
	}
)

func (s *service) GetUserApprovals(limit, offset int) (*resApprovals, error) {
	m, err := s.approvals.GetGroup(limit, offset)
	if err != nil {
		return nil, err
	}
	approvals := make([]resApproval, 0, len(m))
	for _, i := range m {
		approvals = append(approvals, resApproval{
			Userid:       i.Userid,
			Username:     i.Username,
			Email:        i.Email,
			FirstName:    i.FirstName,
			LastName:     i.LastName,
			CreationTime: i.CreationTime,
			Approved:     i.Approved,
			CodeTime:     i.CodeTime,
		})
	}
	return &resApprovals{
		Approvals: approvals,
	}, nil
}

func (s *service) ApproveUser(userid string) error {
	m, err := s.approvals.GetByID(userid)
	if err != nil {
		if governor.ErrorStatus(err) == http.StatusNotFound {
			return governor.NewErrorUser("", 0, err)
		}
		return err
	}
	code, err := s.approvals.RehashCode(m)
	if err != nil {
		return err
	}
	if err := s.approvals.Update(m); err != nil {
		return err
	}
	if err := s.sendNewUserEmail(code, m); err != nil {
		return err
	}
	return nil
}

func (s *service) DeleteUserApproval(userid string) error {
	m, err := s.approvals.GetByID(userid)
	if err != nil {
		if governor.ErrorStatus(err) == http.StatusNotFound {
			return governor.NewErrorUser("", 0, err)
		}
		return err
	}
	if err := s.approvals.Delete(m); err != nil {
		return err
	}
	return nil
}

func (s *service) sendNewUserEmail(code string, m *approvalmodel.Model) error {
	emdata := emailNewUser{
		Userid:    m.Userid,
		Key:       code,
		FirstName: m.FirstName,
		LastName:  m.LastName,
		Username:  m.Username,
	}
	if err := emdata.computeURL(s.emailurlbase, s.tplnewuser); err != nil {
		return err
	}
	if err := s.mailer.Send("", "", []string{m.Email}, newUserTemplate, emdata); err != nil {
		return governor.NewError("Failed to send account verification email", http.StatusInternalServerError, err)
	}
	return nil
}

// CommitUser takes a user from approvals and places it into the user db
func (s *service) CommitUser(userid string, key string) (*resUserUpdate, error) {
	am, err := s.approvals.GetByID(userid)
	if err != nil {
		if governor.ErrorStatus(err) == http.StatusNotFound {
			return nil, governor.NewErrorUser("", 0, err)
		}
		return nil, err
	}
	if !am.Approved {
		return nil, governor.NewErrorUser("Not approved", 0, nil)
	}
	if ok, err := s.approvals.ValidateCode(key, am); err != nil {
		return nil, governor.NewError("Failed to verify key", http.StatusInternalServerError, err)
	} else if !ok {
		return nil, governor.NewErrorUser("Invalid key", http.StatusForbidden, nil)
	}
	m := s.approvals.ToUserModel(am)

	userProps := NewUserProps{
		Userid:       m.Userid,
		Username:     m.Username,
		Email:        m.Email,
		FirstName:    m.FirstName,
		LastName:     m.LastName,
		CreationTime: m.CreationTime,
	}

	b := bytes.Buffer{}
	if err := json.NewEncoder(&b).Encode(userProps); err != nil {
		return nil, governor.NewError("Failed to encode user props to json", http.StatusInternalServerError, err)
	}

	if err := s.users.Insert(m); err != nil {
		if governor.ErrorStatus(err) == http.StatusBadRequest {
			return nil, governor.NewErrorUser("", 0, err)
		}
		return nil, err
	}
	if err := s.roles.InsertRoles(m.Userid, rank.BaseUser()); err != nil {
		return nil, err
	}

	if err := s.queue.Publish(NewUserQueueID, b.Bytes()); err != nil {
		s.logger.Error("Failed to publish new user", map[string]string{
			"error":      err.Error(),
			"actiontype": "publishnewuser",
		})
	}

	if err := s.approvals.Delete(am); err != nil {
		s.logger.Error("Failed to clean up user approval", map[string]string{
			"error":      err.Error(),
			"actiontype": "commitusercleanup",
		})
	}

	s.logger.Info("created user", map[string]string{
		"userid":     m.Userid,
		"username":   m.Username,
		"actiontype": "commituser",
	})

	return &resUserUpdate{
		Userid:   m.Userid,
		Username: m.Username,
	}, nil
}

func (s *service) DeleteUser(userid string, username string, password string) error {
	m, err := s.users.GetByID(userid)
	if err != nil {
		if governor.ErrorStatus(err) == http.StatusNotFound {
			return governor.NewErrorUser("", 0, err)
		}
		return err
	}

	if m.Username != username {
		return governor.NewErrorUser("Information does not match", http.StatusBadRequest, err)
	}
	if roles, err := s.roles.IntersectRoles(userid, rank.Rank{"admin": struct{}{}}); err != nil {
		return err
	} else if roles.Has("admin") {
		return governor.NewErrorUser("Not allowed to delete admin user", http.StatusForbidden, err)
	}
	if ok, err := s.users.ValidatePass(password, m); err != nil {
		return err
	} else if !ok {
		return governor.NewErrorUser("Incorrect password", http.StatusForbidden, nil)
	}

	userProps := DeleteUserProps{
		Userid: m.Userid,
	}
	b := bytes.Buffer{}
	if err := json.NewEncoder(&b).Encode(userProps); err != nil {
		return governor.NewError("Failed to encode user props to json", http.StatusInternalServerError, err)
	}

	if err := s.DeleteUserApikeys(userid); err != nil {
		return err
	}

	if err := s.KillAllSessions(userid); err != nil {
		return err
	}

	if err := s.roles.DeleteAllRoles(userid); err != nil {
		return err
	}

	if err := s.users.Delete(m); err != nil {
		return err
	}

	if err := s.queue.Publish(DeleteUserQueueID, b.Bytes()); err != nil {
		s.logger.Error("failed to publish delete user", map[string]string{
			"error":      err.Error(),
			"actiontype": "publishdeleteuser",
		})
	}
	return nil
}

// DecodeNewUserProps marshals json encoded new user props into a struct
func DecodeNewUserProps(msgdata []byte) (*NewUserProps, error) {
	m := &NewUserProps{}
	if err := json.NewDecoder(bytes.NewBuffer(msgdata)).Decode(m); err != nil {
		return nil, governor.NewError("Failed to decode new user props", http.StatusInternalServerError, err)
	}
	return m, nil
}

// DecodeDeleteUserProps marshals json encoded delete user props into a struct
func DecodeDeleteUserProps(msgdata []byte) (*DeleteUserProps, error) {
	m := &DeleteUserProps{}
	if err := json.NewDecoder(bytes.NewBuffer(msgdata)).Decode(m); err != nil {
		return nil, governor.NewError("Failed to decode delete user props", http.StatusInternalServerError, err)
	}
	return m, nil
}
<|endoftext|>


<commit_before>package pixelgl

import (
	"image/color"
	"runtime"

	"github.com/faiface/glhf"
	"github.com/faiface/mainthread"
	"github.com/faiface/pixel"
	"github.com/go-gl/glfw/v3.2/glfw"
	"github.com/pkg/errors"
)

// WindowConfig is a structure for specifying all possible properties of a Window. Properties are
// chosen in such a way, that you usually only need to set a few of them - defaults (zeros) should
// usually be sensible.
//
// Note that you always need to set the Bounds of a Window.
type WindowConfig struct {
	// Title at the top of the Window.
	Title string

	// Bounds specify the bounds of the Window in pixels.
	Bounds pixel.Rect

	// If set to nil, the Window will be windowed. Otherwise it will be fullscreen on the
	// specified Monitor.
	Monitor *Monitor

	// Whether the Window is resizable.
	Resizable bool

	// Undecorated Window ommits the borders and decorations (close button, etc.).
	Undecorated bool

	// VSync (vertical synchronization) synchronizes Window's framerate with the framerate of
	// the monitor.
	VSync bool
}

// Window is a window handler. Use this type to manipulate a window (input, drawing, etc.).
type Window struct {
	window *glfw.Window

	bounds pixel.Rect
	canvas *Canvas
	vsync  bool

	// need to save these to correctly restore a fullscreen window
	restore struct {
		xpos, ypos, width, height int
	}

	prevInp, tempInp, currInp struct {
		mouse   pixel.Vec
		buttons [KeyLast + 1]bool
		scroll  pixel.Vec
	}
}

var currWin *Window

// NewWindow creates a new Window with it's properties specified in the provided config.
//
// If Window creation fails, an error is returned (e.g. due to unavailable graphics device).
func NewWindow(cfg WindowConfig) (*Window, error) {
	bool2int := map[bool]int{
		true:  glfw.True,
		false: glfw.False,
	}

	w := &Window{bounds: cfg.Bounds}

	err := mainthread.CallErr(func() error {
		var err error

		glfw.WindowHint(glfw.ContextVersionMajor, 3)
		glfw.WindowHint(glfw.ContextVersionMinor, 3)
		glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
		glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)

		glfw.WindowHint(glfw.Resizable, bool2int[cfg.Resizable])
		glfw.WindowHint(glfw.Decorated, bool2int[!cfg.Undecorated])

		var share *glfw.Window
		if currWin != nil {
			share = currWin.window
		}
		_, _, width, height := intBounds(cfg.Bounds)
		w.window, err = glfw.CreateWindow(
			width,
			height,
			cfg.Title,
			nil,
			share,
		)
		if err != nil {
			return err
		}

		// enter the OpenGL context
		w.begin()
		w.end()

		return nil
	})
	if err != nil {
		return nil, errors.Wrap(err, "creating window failed")
	}

	w.SetVSync(cfg.VSync)

	w.initInput()
	w.SetMonitor(cfg.Monitor)

	w.canvas = NewCanvas(cfg.Bounds)
	w.Update()

	runtime.SetFinalizer(w, (*Window).Destroy)

	return w, nil
}

// Destroy destroys the Window. The Window can't be used any further.
func (w *Window) Destroy() {
	mainthread.Call(func() {
		w.window.Destroy()
	})
}

// Update swaps buffers and polls events. Call this method at the end of each frame.
func (w *Window) Update() {
	mainthread.Call(func() {
		_, _, oldW, oldH := intBounds(w.bounds)
		newW, newH := w.window.GetSize()
		w.bounds = w.bounds.ResizedMin(w.bounds.Size() + pixel.V(
			float64(newW-oldW),
			float64(newH-oldH),
		))
	})

	w.canvas.SetBounds(w.bounds)

	mainthread.Call(func() {
		w.begin()

		framebufferWidth, framebufferHeight := w.window.GetFramebufferSize()
		glhf.Bounds(0, 0, framebufferWidth, framebufferHeight)

		glhf.Clear(0, 0, 0, 0)
		w.canvas.gf.Frame().Begin()
		w.canvas.gf.Frame().Blit(
			nil,
			0, 0, w.canvas.Texture().Width(), w.canvas.Texture().Height(),
			0, 0, framebufferWidth, framebufferHeight,
		)
		w.canvas.gf.Frame().End()

		if w.vsync {
			glfw.SwapInterval(1)
		} else {
			glfw.SwapInterval(0)
		}
		w.window.SwapBuffers()
		w.end()
	})

	w.updateInput()
}

// SetClosed sets the closed flag of the Window.
//
// This is useful when overriding the user's attempt to close the Window, or just to close the
// Window from within the program.
func (w *Window) SetClosed(closed bool) {
	mainthread.Call(func() {
		w.window.SetShouldClose(closed)
	})
}

// Closed returns the closed flag of the Window, which reports whether the Window should be closed.
//
// The closed flag is automatically set when a user attempts to close the Window.
func (w *Window) Closed() bool {
	var closed bool
	mainthread.Call(func() {
		closed = w.window.ShouldClose()
	})
	return closed
}

// SetTitle changes the title of the Window.
func (w *Window) SetTitle(title string) {
	mainthread.Call(func() {
		w.window.SetTitle(title)
	})
}

// SetBounds sets the bounds of the Window in pixels. Bounds can be fractional, but the actual size
// of the window will be rounded to integers.
func (w *Window) SetBounds(bounds pixel.Rect) {
	w.bounds = bounds
	mainthread.Call(func() {
		_, _, width, height := intBounds(bounds)
		w.window.SetSize(width, height)
	})
}

// Bounds returns the current bounds of the Window.
func (w *Window) Bounds() pixel.Rect {
	return w.bounds
}

func (w *Window) setFullscreen(monitor *Monitor) {
	mainthread.Call(func() {
		w.restore.xpos, w.restore.ypos = w.window.GetPos()
		w.restore.width, w.restore.height = w.window.GetSize()

		mode := monitor.monitor.GetVideoMode()

		w.window.SetMonitor(
			monitor.monitor,
			0,
			0,
			mode.Width,
			mode.Height,
			mode.RefreshRate,
		)
	})
}

func (w *Window) setWindowed() {
	mainthread.Call(func() {
		w.window.SetMonitor(
			nil,
			w.restore.xpos,
			w.restore.ypos,
			w.restore.width,
			w.restore.height,
			0,
		)
	})
}

// SetMonitor sets the Window fullscreen on the given Monitor. If the Monitor is nil, the Window
// will be restored to windowed state instead.
//
// The Window will be automatically set to the Monitor's resolution. If you want a different
// resolution, you will need to set it manually with SetBounds method.
func (w *Window) SetMonitor(monitor *Monitor) {
	if w.Monitor() != monitor {
		if monitor != nil {
			w.setFullscreen(monitor)
		} else {
			w.setWindowed()
		}
	}
}

// Monitor returns a monitor the Window is fullscreen on. If the Window is not fullscreen, this
// function returns nil.
func (w *Window) Monitor() *Monitor {
	var monitor *glfw.Monitor
	mainthread.Call(func() {
		monitor = w.window.GetMonitor()
	})
	if monitor == nil {
		return nil
	}
	return &Monitor{
		monitor: monitor,
	}
}

// Focused returns true if the Window has input focus.
func (w *Window) Focused() bool {
	var focused bool
	mainthread.Call(func() {
		focused = w.window.GetAttrib(glfw.Focused) == glfw.True
	})
	return focused
}

// SetVSync sets whether the Window's Update should synchronize with the monitor refresh rate.
func (w *Window) SetVSync(vsync bool) {
	w.vsync = vsync
}

// VSync returns whether the Window is set to synchronize with the monitor refresh rate.
func (w *Window) VSync() bool {
	return w.vsync
}

// Note: must be called inside the main thread.
func (w *Window) begin() {
	if currWin != w {
		w.window.MakeContextCurrent()
		glhf.Init()
		currWin = w
	}
}

// Note: must be called inside the main thread.
func (w *Window) end() {
	// nothing, really
}

// MakeTriangles generates a specialized copy of the supplied Triangles that will draw onto this
// Window.
//
// Window supports TrianglesPosition, TrianglesColor and TrianglesPicture.
func (w *Window) MakeTriangles(t pixel.Triangles) pixel.TargetTriangles {
	return w.canvas.MakeTriangles(t)
}

// MakePicture generates a specialized copy of the supplied Picture that will draw onto this Window.
//
// Window supports PictureColor.
func (w *Window) MakePicture(p pixel.Picture) pixel.TargetPicture {
	return w.canvas.MakePicture(p)
}

// SetMatrix sets a Matrix that every point will be projected by.
func (w *Window) SetMatrix(m pixel.Matrix) {
	w.canvas.SetMatrix(m)
}

// SetColorMask sets a global color mask for the Window.
func (w *Window) SetColorMask(c color.Color) {
	w.canvas.SetColorMask(c)
}

// SetComposeMethod sets a Porter-Duff composition method to be used in the following draws onto
// this Window.
func (w *Window) SetComposeMethod(cmp pixel.ComposeMethod) {
	w.canvas.SetComposeMethod(cmp)
}

// SetSmooth sets whether the stretched Pictures drawn onto this Window should be drawn smooth or
// pixely.
func (w *Window) SetSmooth(smooth bool) {
	w.canvas.SetSmooth(smooth)
}

// Smooth returns whether the stretched Pictures drawn onto this Window are set to be drawn smooth
// or pixely.
func (w *Window) Smooth() bool {
	return w.canvas.Smooth()
}

// Clear clears the Window with a single color.
func (w *Window) Clear(c color.Color) {
	w.canvas.Clear(c)
}

// Color returns the color of the pixel over the given position inside the Window.
func (w *Window) Color(at pixel.Vec) pixel.RGBA {
	return w.canvas.Color(at)
}
<commit_msg>Add ability to hide the cursor<commit_after>package pixelgl

import (
	"image/color"
	"runtime"

	"github.com/faiface/glhf"
	"github.com/faiface/mainthread"
	"github.com/faiface/pixel"
	"github.com/go-gl/glfw/v3.2/glfw"
	"github.com/pkg/errors"
)

// WindowConfig is a structure for specifying all possible properties of a Window. Properties are
// chosen in such a way, that you usually only need to set a few of them - defaults (zeros) should
// usually be sensible.
//
// Note that you always need to set the Bounds of a Window.
type WindowConfig struct {
	// Title at the top of the Window.
	Title string

	// Bounds specify the bounds of the Window in pixels.
	Bounds pixel.Rect

	// If set to nil, the Window will be windowed. Otherwise it will be fullscreen on the
	// specified Monitor.
	Monitor *Monitor

	// Whether the Window is resizable.
	Resizable bool

	// Undecorated Window ommits the borders and decorations (close button, etc.).
	Undecorated bool

	// VSync (vertical synchronization) synchronizes Window's framerate with the framerate of
	// the monitor.
	VSync bool
}

// Window is a window handler. Use this type to manipulate a window (input, drawing, etc.).
type Window struct {
	window *glfw.Window

	bounds        pixel.Rect
	canvas        *Canvas
	vsync         bool
	cursorVisible bool

	// need to save these to correctly restore a fullscreen window
	restore struct {
		xpos, ypos, width, height int
	}

	prevInp, tempInp, currInp struct {
		mouse   pixel.Vec
		buttons [KeyLast + 1]bool
		scroll  pixel.Vec
	}
}

var currWin *Window

// NewWindow creates a new Window with it's properties specified in the provided config.
//
// If Window creation fails, an error is returned (e.g. due to unavailable graphics device).
func NewWindow(cfg WindowConfig) (*Window, error) {
	bool2int := map[bool]int{
		true:  glfw.True,
		false: glfw.False,
	}

	w := &Window{bounds: cfg.Bounds}

	err := mainthread.CallErr(func() error {
		var err error

		glfw.WindowHint(glfw.ContextVersionMajor, 3)
		glfw.WindowHint(glfw.ContextVersionMinor, 3)
		glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
		glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)

		glfw.WindowHint(glfw.Resizable, bool2int[cfg.Resizable])
		glfw.WindowHint(glfw.Decorated, bool2int[!cfg.Undecorated])

		var share *glfw.Window
		if currWin != nil {
			share = currWin.window
		}
		_, _, width, height := intBounds(cfg.Bounds)
		w.window, err = glfw.CreateWindow(
			width,
			height,
			cfg.Title,
			nil,
			share,
		)
		if err != nil {
			return err
		}

		// enter the OpenGL context
		w.begin()
		w.end()

		return nil
	})
	if err != nil {
		return nil, errors.Wrap(err, "creating window failed")
	}

	w.SetVSync(cfg.VSync)

	w.initInput()
	w.SetMonitor(cfg.Monitor)

	w.canvas = NewCanvas(cfg.Bounds)
	w.Update()

	runtime.SetFinalizer(w, (*Window).Destroy)

	return w, nil
}

// Destroy destroys the Window. The Window can't be used any further.
func (w *Window) Destroy() {
	mainthread.Call(func() {
		w.window.Destroy()
	})
}

// Update swaps buffers and polls events. Call this method at the end of each frame.
func (w *Window) Update() {
	mainthread.Call(func() {
		_, _, oldW, oldH := intBounds(w.bounds)
		newW, newH := w.window.GetSize()
		w.bounds = w.bounds.ResizedMin(w.bounds.Size() + pixel.V(
			float64(newW-oldW),
			float64(newH-oldH),
		))
	})

	w.canvas.SetBounds(w.bounds)

	mainthread.Call(func() {
		w.begin()

		framebufferWidth, framebufferHeight := w.window.GetFramebufferSize()
		glhf.Bounds(0, 0, framebufferWidth, framebufferHeight)

		glhf.Clear(0, 0, 0, 0)
		w.canvas.gf.Frame().Begin()
		w.canvas.gf.Frame().Blit(
			nil,
			0, 0, w.canvas.Texture().Width(), w.canvas.Texture().Height(),
			0, 0, framebufferWidth, framebufferHeight,
		)
		w.canvas.gf.Frame().End()

		if w.vsync {
			glfw.SwapInterval(1)
		} else {
			glfw.SwapInterval(0)
		}
		w.window.SwapBuffers()
		w.end()
	})

	w.updateInput()
}

// SetClosed sets the closed flag of the Window.
//
// This is useful when overriding the user's attempt to close the Window, or just to close the
// Window from within the program.
func (w *Window) SetClosed(closed bool) {
	mainthread.Call(func() {
		w.window.SetShouldClose(closed)
	})
}

// Closed returns the closed flag of the Window, which reports whether the Window should be closed.
//
// The closed flag is automatically set when a user attempts to close the Window.
func (w *Window) Closed() bool {
	var closed bool
	mainthread.Call(func() {
		closed = w.window.ShouldClose()
	})
	return closed
}

// SetTitle changes the title of the Window.
func (w *Window) SetTitle(title string) {
	mainthread.Call(func() {
		w.window.SetTitle(title)
	})
}

// SetBounds sets the bounds of the Window in pixels. Bounds can be fractional, but the actual size
// of the window will be rounded to integers.
func (w *Window) SetBounds(bounds pixel.Rect) {
	w.bounds = bounds
	mainthread.Call(func() {
		_, _, width, height := intBounds(bounds)
		w.window.SetSize(width, height)
	})
}

// Bounds returns the current bounds of the Window.
func (w *Window) Bounds() pixel.Rect {
	return w.bounds
}

func (w *Window) setFullscreen(monitor *Monitor) {
	mainthread.Call(func() {
		w.restore.xpos, w.restore.ypos = w.window.GetPos()
		w.restore.width, w.restore.height = w.window.GetSize()

		mode := monitor.monitor.GetVideoMode()

		w.window.SetMonitor(
			monitor.monitor,
			0,
			0,
			mode.Width,
			mode.Height,
			mode.RefreshRate,
		)
	})
}

func (w *Window) setWindowed() {
	mainthread.Call(func() {
		w.window.SetMonitor(
			nil,
			w.restore.xpos,
			w.restore.ypos,
			w.restore.width,
			w.restore.height,
			0,
		)
	})
}

// SetMonitor sets the Window fullscreen on the given Monitor. If the Monitor is nil, the Window
// will be restored to windowed state instead.
//
// The Window will be automatically set to the Monitor's resolution. If you want a different
// resolution, you will need to set it manually with SetBounds method.
func (w *Window) SetMonitor(monitor *Monitor) {
	if w.Monitor() != monitor {
		if monitor != nil {
			w.setFullscreen(monitor)
		} else {
			w.setWindowed()
		}
	}
}

// Monitor returns a monitor the Window is fullscreen on. If the Window is not fullscreen, this
// function returns nil.
func (w *Window) Monitor() *Monitor {
	var monitor *glfw.Monitor
	mainthread.Call(func() {
		monitor = w.window.GetMonitor()
	})
	if monitor == nil {
		return nil
	}
	return &Monitor{
		monitor: monitor,
	}
}

// Focused returns true if the Window has input focus.
func (w *Window) Focused() bool {
	var focused bool
	mainthread.Call(func() {
		focused = w.window.GetAttrib(glfw.Focused) == glfw.True
	})
	return focused
}

// SetVSync sets whether the Window's Update should synchronize with the monitor refresh rate.
func (w *Window) SetVSync(vsync bool) {
	w.vsync = vsync
}

// VSync returns whether the Window is set to synchronize with the monitor refresh rate.
func (w *Window) VSync() bool {
	return w.vsync
}

// SetCursorVisible sets the visibility of the mouse cursor inside the window client area
func (w *Window) SetCursorVisible(visible bool) {
	w.cursorVisible = visible
	mainthread.Call(func() {
		if visible && w.window.GetInputMode(glfw.CursorMode) != glfw.CursorNormal {
			w.window.SetInputMode(glfw.CursorMode, glfw.CursorNormal)
		}
		if !visible && w.window.GetInputMode(glfw.CursorMode) != glfw.CursorHidden {
			w.window.SetInputMode(glfw.CursorMode, glfw.CursorHidden)
		}
	})
}

// CursorVisible returns the visibility status of the mouse cursor
func (w *Window) CursorVisible() bool {
	return w.cursorVisible
}

// Note: must be called inside the main thread.
func (w *Window) begin() {
	if currWin != w {
		w.window.MakeContextCurrent()
		glhf.Init()
		currWin = w
	}
}

// Note: must be called inside the main thread.
func (w *Window) end() {
	// nothing, really
}

// MakeTriangles generates a specialized copy of the supplied Triangles that will draw onto this
// Window.
//
// Window supports TrianglesPosition, TrianglesColor and TrianglesPicture.
func (w *Window) MakeTriangles(t pixel.Triangles) pixel.TargetTriangles {
	return w.canvas.MakeTriangles(t)
}

// MakePicture generates a specialized copy of the supplied Picture that will draw onto this Window.
//
// Window supports PictureColor.
func (w *Window) MakePicture(p pixel.Picture) pixel.TargetPicture {
	return w.canvas.MakePicture(p)
}

// SetMatrix sets a Matrix that every point will be projected by.
func (w *Window) SetMatrix(m pixel.Matrix) {
	w.canvas.SetMatrix(m)
}

// SetColorMask sets a global color mask for the Window.
func (w *Window) SetColorMask(c color.Color) {
	w.canvas.SetColorMask(c)
}

// SetComposeMethod sets a Porter-Duff composition method to be used in the following draws onto
// this Window.
func (w *Window) SetComposeMethod(cmp pixel.ComposeMethod) {
	w.canvas.SetComposeMethod(cmp)
}

// SetSmooth sets whether the stretched Pictures drawn onto this Window should be drawn smooth or
// pixely.
func (w *Window) SetSmooth(smooth bool) {
	w.canvas.SetSmooth(smooth)
}

// Smooth returns whether the stretched Pictures drawn onto this Window are set to be drawn smooth
// or pixely.
func (w *Window) Smooth() bool {
	return w.canvas.Smooth()
}

// Clear clears the Window with a single color.
func (w *Window) Clear(c color.Color) {
	w.canvas.Clear(c)
}

// Color returns the color of the pixel over the given position inside the Window.
func (w *Window) Color(at pixel.Vec) pixel.RGBA {
	return w.canvas.Color(at)
}
<|endoftext|>