<commit_before>package user

import (
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/fubarhouse/golang-drush/command"
	"strings"
)

// DrupalUser represents fields from Drupals user table, as well as roles.
type DrupalUser struct {
	Alias string
	UID   int
	Name  string
	Email string
	State int
	Roles []string
}

// NewDrupalUser generates a new DrupalUser object.
func NewDrupalUser() DrupalUser {
	return DrupalUser{}
}

// SetRoles will allocate a valid and accurate value to the Roles field in a given DrupalUser object.
func (DrupalUser *DrupalUser) SetRoles() {
	var RolesCommand = fmt.Sprintf("user-information '%v' --fields=roles | cut -d: -f2", DrupalUser.Name)
	cmd := command.NewDrushCommand()
	cmd.Set(DrupalUser.Alias, RolesCommand, false)
	cmdRolesOut, cmdRolesErr := cmd.CombinedOutput()
	if cmdRolesErr != nil {
		log.Errorln("Could not execute Drush user-information:", cmdRolesErr.Error())
	}
	Roles := []string{}
	for _, Role := range strings.Split(string(cmdRolesOut), "\n") {
		Role = strings.TrimSpace(Role)
		if Role != "" {
			Roles = append(Roles, Role)
		}
	}
	DrupalUser.Roles = Roles
}

// Delete will delete a user from a Drupal site, but only if it exists.
func (DrupalUser *DrupalUser) Delete() {
	UserGroup := NewDrupalUserGroup()
	UserGroup.Populate(DrupalUser.Alias)
	if UserGroup.FindUser(DrupalUser.Name) {
		var Command = fmt.Sprintf("user-cancel --yes '%v'", DrupalUser.Name)
		cmd := command.NewDrushCommand()
		cmd.Set(DrupalUser.Alias, Command, false)
		_, cmdErr := cmd.CombinedOutput()
		if cmdErr != nil {
			log.Warnf("Could not remove user %v on site %v: %v", DrupalUser.Name, DrupalUser.Alias, cmdErr.Error())
		} else {
			log.Infof("Removed user %v on site %v.", DrupalUser.Name, DrupalUser.Alias)
		}
	}
}

// Create will create a user from a Drupal site, but only if does not exist.
func (DrupalUser *DrupalUser) Create(Password string) {
	UserGroup := NewDrupalUserGroup()
	UserGroup.Populate(DrupalUser.Alias)
	if !UserGroup.FindUser(DrupalUser.Name) {
		var Command = fmt.Sprintf("user-create '%v' --mail='%v' --password='%v'", DrupalUser.Name, DrupalUser.Email, Password)
		cmd := command.NewDrushCommand()
		cmd.Set(DrupalUser.Alias, Command, false)
		_, cmdErr := cmd.CombinedOutput()
		if cmdErr != nil {
			log.Warnf("Could not create user %v on site %v: %v", DrupalUser.Name, DrupalUser.Alias, cmdErr.Error())
		} else {
			log.Infof("Created user %v on site %v.", DrupalUser.Name, DrupalUser.Alias)
		}
	}
}

// StateChange will change the status of the user to the value specified in *DrupalUser.State
// There is a built-in verification process here, so a separate verification method is not required.
func (DrupalUser *DrupalUser) StateChange() {
	// Get the absolutely correct User object.
	UserGroup := NewDrupalUserGroup()
	UserGroup.Populate(DrupalUser.Alias)
	User := UserGroup.GetUser(DrupalUser.Name)

	if User.State != DrupalUser.State {
		State := "user-block"
		if User.State == 0 {
			State = "user-unblock"
		}
		cmd := command.NewDrushCommand()
		var Command = fmt.Sprintf("%v '%v'", State, DrupalUser.Name)
		cmd.Set(DrupalUser.Alias, Command, false)
		_, cmdErr := cmd.CombinedOutput()
		if cmdErr != nil {
			log.Warnf("Could not perform action %v for user %v on site %v: %v", State, DrupalUser.Name, DrupalUser.Alias, cmdErr.Error())
		} else {
			log.Infof("Performed action %v for user %v on site %v", State, DrupalUser.Name, DrupalUser.Alias)
		}
	}
}

// SetPassword will set the password of a user.
// Action will be performed, as there is no password validation available.
func (DrupalUser *DrupalUser) SetPassword(Password string) {
	var Command = fmt.Sprintf("user-password \"%v\" --password=\"%v\"", DrupalUser.Name, Password)
	cmd := command.NewDrushCommand()
	cmd.Set(DrupalUser.Alias, Command, false)
	_, cmdErr := cmd.CombinedOutput()
	if cmdErr != nil {
		log.Warnf("Could not complete password change for user %v on site %v: %v", DrupalUser.Name, DrupalUser.Alias, cmdErr.Error())
	} else {
		log.Infof("Password for user %v on site %v has been changed.", DrupalUser.Name, DrupalUser.Alias)
	}
}

// EmailChange will change the email of the target if the email address
// does not match the email address in the DrupalUser object.
func (DrupalUser *DrupalUser) EmailChange() {
	UserGroup := NewDrupalUserGroup()
	UserGroup.Populate(DrupalUser.Alias)
	User := UserGroup.GetUser(DrupalUser.Name)
	if User.Email != DrupalUser.Email && UserGroup.FindUser(DrupalUser.Name) {
		var Command = "sqlq \"UPDATE users SET init='" + User.Email + "', mail='" + DrupalUser.Email + "' WHERE name='" + DrupalUser.Name + "';\""
		cmd := command.NewDrushCommand()
		cmd.Set(DrupalUser.Alias, Command, false)
		_, cmdErr := cmd.CombinedOutput()
		if cmdErr != nil {
			log.Warnf("Could not change email for user %v on site %v from %v to %v: %v", DrupalUser.Name, DrupalUser.Alias, User.Email, DrupalUser.Email, cmdErr.Error())
		} else {
			log.Infof("Changed email for user %v on site %v from %v to %v, clear caches if results are unexpected.", DrupalUser.Name, DrupalUser.Alias, User.Email, DrupalUser.Email)
		}
	}
}

// HasRole will determine if the user has a given String in the list of roles, which will return as a Boolean.
func (DrupalUser *DrupalUser) HasRole(Role string) bool {
	for _, value := range DrupalUser.Roles {
		if value == Role {
			return true
		}
	}
	return false
}

// RolesAdd will add all associated roles to the target user,
// when not present in the DrupalUser object.
func (DrupalUser *DrupalUser) RolesAdd() {
	UserGroup := NewDrupalUserGroup()
	UserGroup.Populate(DrupalUser.Alias)
	User := UserGroup.GetUser(DrupalUser.Name)
	User.SetRoles()
	for _, Role := range DrupalUser.Roles {
		if Role != "authenticated user" {
			if !User.HasRole(Role) {
				var Command = fmt.Sprintf("user-add-role --name='%v' '%v'", DrupalUser.Name, Role)
				cmd := command.NewDrushCommand()
				cmd.Set(DrupalUser.Alias, Command, false)
				_, cmdErr := cmd.CombinedOutput()
				if cmdErr != nil {
					log.Warnf("Could not add role %v to use %v on site %v: %v", Role, DrupalUser.Name, DrupalUser.Alias, cmdErr.Error())
				} else {
					log.Infof("Added user %v to role %v on site %v.", DrupalUser.Name, Role, DrupalUser.Alias)
				}
			}
		}
	}
}

// RolesRemove will remove all associated roles to the target user,
// when present in the DrupalUser object.
func (DrupalUser *DrupalUser) RolesRemove() {
	// if not "authenticated user" {
	// if user has role, and the role needs to be removed, remove the role. {
	UserGroup := NewDrupalUserGroup()
	UserGroup.Populate(DrupalUser.Alias)
	User := UserGroup.GetUser(DrupalUser.Name)
	User.SetRoles()
	for _, Role := range DrupalUser.Roles {
		if Role != "authenticated user" {
			if User.HasRole(Role) {
				var Command = fmt.Sprintf("user-remove-role --name='%v' '%v'", DrupalUser.Name, Role)
				cmd := command.NewDrushCommand()
				cmd.Set(DrupalUser.Alias, Command, false)
				_, cmdErr := cmd.CombinedOutput()
				if cmdErr != nil {
					log.Warnf("Could not remove role %v on user %v on site %v: %v", Role, DrupalUser.Name, DrupalUser.Alias, cmdErr.Error())
				} else {
					log.Infof("Removed user %v from role %v on site %v.", DrupalUser.Name, Role, DrupalUser.Alias)
				}
			}
		}
	}
}
<commit_msg>Add some debugging messaging on user creation<commit_after>package user

import (
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/fubarhouse/golang-drush/command"
	"strings"
)

// DrupalUser represents fields from Drupals user table, as well as roles.
type DrupalUser struct {
	Alias string
	UID   int
	Name  string
	Email string
	State int
	Roles []string
}

// NewDrupalUser generates a new DrupalUser object.
func NewDrupalUser() DrupalUser {
	return DrupalUser{}
}

// SetRoles will allocate a valid and accurate value to the Roles field in a given DrupalUser object.
func (DrupalUser *DrupalUser) SetRoles() {
	var RolesCommand = fmt.Sprintf("user-information '%v' --fields=roles | cut -d: -f2", DrupalUser.Name)
	cmd := command.NewDrushCommand()
	cmd.Set(DrupalUser.Alias, RolesCommand, false)
	cmdRolesOut, cmdRolesErr := cmd.CombinedOutput()
	if cmdRolesErr != nil {
		log.Errorln("Could not execute Drush user-information:", cmdRolesErr.Error())
	}
	Roles := []string{}
	for _, Role := range strings.Split(string(cmdRolesOut), "\n") {
		Role = strings.TrimSpace(Role)
		if Role != "" {
			Roles = append(Roles, Role)
		}
	}
	DrupalUser.Roles = Roles
}

// Delete will delete a user from a Drupal site, but only if it exists.
func (DrupalUser *DrupalUser) Delete() {
	UserGroup := NewDrupalUserGroup()
	UserGroup.Populate(DrupalUser.Alias)
	if UserGroup.FindUser(DrupalUser.Name) {
		var Command = fmt.Sprintf("user-cancel --yes '%v'", DrupalUser.Name)
		cmd := command.NewDrushCommand()
		cmd.Set(DrupalUser.Alias, Command, false)
		_, cmdErr := cmd.CombinedOutput()
		if cmdErr != nil {
			log.Warnf("Could not remove user %v on site %v: %v", DrupalUser.Name, DrupalUser.Alias, cmdErr.Error())
		} else {
			log.Infof("Removed user %v on site %v.", DrupalUser.Name, DrupalUser.Alias)
		}
	}
}

// Create will create a user from a Drupal site, but only if does not exist.
func (DrupalUser *DrupalUser) Create(Password string) {
	UserGroup := NewDrupalUserGroup()
	UserGroup.Populate(DrupalUser.Alias)
	if !UserGroup.FindUser(DrupalUser.Name) {
		var Command = fmt.Sprintf("user-create '%v' --mail='%v' --password='%v'", DrupalUser.Name, DrupalUser.Email, Password)
		cmd := command.NewDrushCommand()
		cmd.Set(DrupalUser.Alias, Command, false)
		cmdOut, cmdErr := cmd.CombinedOutput()
		if cmdErr != nil {
			log.Warnf("Could not create user %v on site %v: %v: %v", DrupalUser.Name, DrupalUser.Alias, cmdErr.Error(), cmdOut)
		} else {
			log.Infof("Created user %v on site %v.", DrupalUser.Name, DrupalUser.Alias)
		}
	}
}

// StateChange will change the status of the user to the value specified in *DrupalUser.State
// There is a built-in verification process here, so a separate verification method is not required.
func (DrupalUser *DrupalUser) StateChange() {
	// Get the absolutely correct User object.
	UserGroup := NewDrupalUserGroup()
	UserGroup.Populate(DrupalUser.Alias)
	User := UserGroup.GetUser(DrupalUser.Name)

	if User.State != DrupalUser.State {
		State := "user-block"
		if User.State == 0 {
			State = "user-unblock"
		}
		cmd := command.NewDrushCommand()
		var Command = fmt.Sprintf("%v '%v'", State, DrupalUser.Name)
		cmd.Set(DrupalUser.Alias, Command, false)
		_, cmdErr := cmd.CombinedOutput()
		if cmdErr != nil {
			log.Warnf("Could not perform action %v for user %v on site %v: %v", State, DrupalUser.Name, DrupalUser.Alias, cmdErr.Error())
		} else {
			log.Infof("Performed action %v for user %v on site %v", State, DrupalUser.Name, DrupalUser.Alias)
		}
	}
}

// SetPassword will set the password of a user.
// Action will be performed, as there is no password validation available.
func (DrupalUser *DrupalUser) SetPassword(Password string) {
	var Command = fmt.Sprintf("user-password \"%v\" --password=\"%v\"", DrupalUser.Name, Password)
	cmd := command.NewDrushCommand()
	cmd.Set(DrupalUser.Alias, Command, false)
	_, cmdErr := cmd.CombinedOutput()
	if cmdErr != nil {
		log.Warnf("Could not complete password change for user %v on site %v: %v", DrupalUser.Name, DrupalUser.Alias, cmdErr.Error())
	} else {
		log.Infof("Password for user %v on site %v has been changed.", DrupalUser.Name, DrupalUser.Alias)
	}
}

// EmailChange will change the email of the target if the email address
// does not match the email address in the DrupalUser object.
func (DrupalUser *DrupalUser) EmailChange() {
	UserGroup := NewDrupalUserGroup()
	UserGroup.Populate(DrupalUser.Alias)
	User := UserGroup.GetUser(DrupalUser.Name)
	if User.Email != DrupalUser.Email && UserGroup.FindUser(DrupalUser.Name) {
		var Command = "sqlq \"UPDATE users SET init='" + User.Email + "', mail='" + DrupalUser.Email + "' WHERE name='" + DrupalUser.Name + "';\""
		cmd := command.NewDrushCommand()
		cmd.Set(DrupalUser.Alias, Command, false)
		_, cmdErr := cmd.CombinedOutput()
		if cmdErr != nil {
			log.Warnf("Could not change email for user %v on site %v from %v to %v: %v", DrupalUser.Name, DrupalUser.Alias, User.Email, DrupalUser.Email, cmdErr.Error())
		} else {
			log.Infof("Changed email for user %v on site %v from %v to %v, clear caches if results are unexpected.", DrupalUser.Name, DrupalUser.Alias, User.Email, DrupalUser.Email)
		}
	}
}

// HasRole will determine if the user has a given String in the list of roles, which will return as a Boolean.
func (DrupalUser *DrupalUser) HasRole(Role string) bool {
	for _, value := range DrupalUser.Roles {
		if value == Role {
			return true
		}
	}
	return false
}

// RolesAdd will add all associated roles to the target user,
// when not present in the DrupalUser object.
func (DrupalUser *DrupalUser) RolesAdd() {
	UserGroup := NewDrupalUserGroup()
	UserGroup.Populate(DrupalUser.Alias)
	User := UserGroup.GetUser(DrupalUser.Name)
	User.SetRoles()
	for _, Role := range DrupalUser.Roles {
		if Role != "authenticated user" {
			if !User.HasRole(Role) {
				var Command = fmt.Sprintf("user-add-role --name='%v' '%v'", DrupalUser.Name, Role)
				cmd := command.NewDrushCommand()
				cmd.Set(DrupalUser.Alias, Command, false)
				_, cmdErr := cmd.CombinedOutput()
				if cmdErr != nil {
					log.Warnf("Could not add role %v to use %v on site %v: %v", Role, DrupalUser.Name, DrupalUser.Alias, cmdErr.Error())
				} else {
					log.Infof("Added user %v to role %v on site %v.", DrupalUser.Name, Role, DrupalUser.Alias)
				}
			}
		}
	}
}

// RolesRemove will remove all associated roles to the target user,
// when present in the DrupalUser object.
func (DrupalUser *DrupalUser) RolesRemove() {
	// if not "authenticated user" {
	// if user has role, and the role needs to be removed, remove the role. {
	UserGroup := NewDrupalUserGroup()
	UserGroup.Populate(DrupalUser.Alias)
	User := UserGroup.GetUser(DrupalUser.Name)
	User.SetRoles()
	for _, Role := range DrupalUser.Roles {
		if Role != "authenticated user" {
			if User.HasRole(Role) {
				var Command = fmt.Sprintf("user-remove-role --name='%v' '%v'", DrupalUser.Name, Role)
				cmd := command.NewDrushCommand()
				cmd.Set(DrupalUser.Alias, Command, false)
				_, cmdErr := cmd.CombinedOutput()
				if cmdErr != nil {
					log.Warnf("Could not remove role %v on user %v on site %v: %v", Role, DrupalUser.Name, DrupalUser.Alias, cmdErr.Error())
				} else {
					log.Infof("Removed user %v from role %v on site %v.", DrupalUser.Name, Role, DrupalUser.Alias)
				}
			}
		}
	}
}
<|endoftext|>


<commit_before>package subscriptions

import (
	"encoding/json"
	"github.com/notegio/openrelay/channels"
	"github.com/notegio/openrelay/db"
	"log"
	"errors"
)

type SubscriptionConsumer struct {
	manager *SubscriptionManager
	publisher channels.Publisher
	baseFilter *OrderFilter
	exchangeLookup ExchangeLookup
}

func NewSubscriptionConsumer(manager *SubscriptionManager, publisher channels.Publisher, filter *OrderFilter, lookup ExchangeLookup) (*SubscriptionConsumer) {
	return &SubscriptionConsumer{manager, publisher, filter, lookup}
}

type SubscriptionMessage struct {
	Type      string `json:"type"`
	Channel   string `json:"channel"`
	RequestID string `json:"requestId"`
	Payload   *OrderFilter
}

func sendError(publisher channels.Publisher, err error) {
	message := SubscriptionUpdate{
		Type: "error",
		Channel: "orders",
		RequestID: "unknown",
		Payload: []interface{}{err.Error()},
	}
	data, _ := json.Marshal(message)
	publisher.Publish(string(data))
}

func (consumer *SubscriptionConsumer) Consume(delivery channels.Delivery) {
	incoming := &SubscriptionMessage{}
	if err := json.Unmarshal([]byte(delivery.Payload()), incoming); err != nil {
		log.Printf("Error parsing JSON: %v", err.Error())
		sendError(consumer.publisher, err)
		return
	}
	if incoming.Type != "subscribe" {
		log.Printf("Invalid message type '%v'", incoming.Type)
		sendError(consumer.publisher, errors.New("Invalid message type"))
		return
	}
	if incoming.Channel != "orders" {
		log.Printf("Invalid channel '%v'", incoming.Channel)
		sendError(consumer.publisher, errors.New("Invalid channel"))
		return
	}
	if incoming.RequestID == "" || len(incoming.RequestID) > 20 {
		log.Printf("Invalid request ID")
		sendError(consumer.publisher, errors.New("Invalid request ID (check length)"))
		return
	}
	baseFilterFn := func(order *db.Order) (bool) { return true }
	payloadFilterFn := func(order *db.Order) (bool) { return true }
	if consumer.baseFilter != nil {
		var err error
		baseFilterFn, err = consumer.baseFilter.GetFilter(consumer.exchangeLookup)
		if err != nil {
			log.Printf("Error getting filter %v", err.Error())
			sendError(consumer.publisher, err)
			return
		}
	}
	if incoming.Payload != nil {
		var err error
		payloadFilterFn, err = incoming.Payload.GetFilter(consumer.exchangeLookup)
		if err != nil {
			log.Printf("Error getting filter %v", err.Error())
			sendError(consumer.publisher, err)
			return
		}
	}
	subscription := Subscription{
		publisher: consumer.publisher,
		requestID: incoming.RequestID,
		filter: func(order *db.Order) (bool) { return baseFilterFn(order) && payloadFilterFn(order) },
	}
	consumer.manager.Add(subscription)
}
<commit_msg>Make sure request ID limit can handle UUIDs<commit_after>package subscriptions

import (
	"encoding/json"
	"github.com/notegio/openrelay/channels"
	"github.com/notegio/openrelay/db"
	"log"
	"errors"
)

type SubscriptionConsumer struct {
	manager *SubscriptionManager
	publisher channels.Publisher
	baseFilter *OrderFilter
	exchangeLookup ExchangeLookup
}

func NewSubscriptionConsumer(manager *SubscriptionManager, publisher channels.Publisher, filter *OrderFilter, lookup ExchangeLookup) (*SubscriptionConsumer) {
	return &SubscriptionConsumer{manager, publisher, filter, lookup}
}

type SubscriptionMessage struct {
	Type      string `json:"type"`
	Channel   string `json:"channel"`
	RequestID string `json:"requestId"`
	Payload   *OrderFilter
}

func sendError(publisher channels.Publisher, err error) {
	message := SubscriptionUpdate{
		Type: "error",
		Channel: "orders",
		RequestID: "unknown",
		Payload: []interface{}{err.Error()},
	}
	data, _ := json.Marshal(message)
	publisher.Publish(string(data))
}

func (consumer *SubscriptionConsumer) Consume(delivery channels.Delivery) {
	incoming := &SubscriptionMessage{}
	if err := json.Unmarshal([]byte(delivery.Payload()), incoming); err != nil {
		log.Printf("Error parsing JSON: %v", err.Error())
		sendError(consumer.publisher, err)
		return
	}
	if incoming.Type != "subscribe" {
		log.Printf("Invalid message type '%v'", incoming.Type)
		sendError(consumer.publisher, errors.New("Invalid message type"))
		return
	}
	if incoming.Channel != "orders" {
		log.Printf("Invalid channel '%v'", incoming.Channel)
		sendError(consumer.publisher, errors.New("Invalid channel"))
		return
	}
	if incoming.RequestID == "" || len(incoming.RequestID) > 64 {
		log.Printf("Invalid request ID")
		sendError(consumer.publisher, errors.New("Invalid request ID (check length)"))
		return
	}
	baseFilterFn := func(order *db.Order) (bool) { return true }
	payloadFilterFn := func(order *db.Order) (bool) { return true }
	if consumer.baseFilter != nil {
		var err error
		baseFilterFn, err = consumer.baseFilter.GetFilter(consumer.exchangeLookup)
		if err != nil {
			log.Printf("Error getting filter %v", err.Error())
			sendError(consumer.publisher, err)
			return
		}
	}
	if incoming.Payload != nil {
		var err error
		payloadFilterFn, err = incoming.Payload.GetFilter(consumer.exchangeLookup)
		if err != nil {
			log.Printf("Error getting filter %v", err.Error())
			sendError(consumer.publisher, err)
			return
		}
	}
	subscription := Subscription{
		publisher: consumer.publisher,
		requestID: incoming.RequestID,
		filter: func(order *db.Order) (bool) { return baseFilterFn(order) && payloadFilterFn(order) },
	}
	consumer.manager.Add(subscription)
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/flynn/go-flynn/migrate"
	"github.com/flynn/go-sql"
)

func migrateDB(db *sql.DB) error {
	m := migrate.NewMigrations()
	m.Add(1,
		`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`,
		`CREATE EXTENSION IF NOT EXISTS "hstore"`,

		`CREATE TABLE artifacts (
    artifact_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
    type text NOT NULL,
    uri text NOT NULL,
    created_at timestamptz NOT NULL DEFAULT now(),
    deleted_at timestamptz,
    UNIQUE (type, uri)
)`,

		`CREATE TABLE releases (
    release_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
    artifact_id uuid NOT NULL REFERENCES artifacts (artifact_id),
    data text NOT NULL,
    created_at timestamptz NOT NULL DEFAULT now(),
    deleted_at timestamptz
)`,

		`CREATE TABLE apps (
    app_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
    name text UNIQUE NOT NULL,
    release_id uuid REFERENCES releases (release_id),
    protected bool NOT NULL DEFAULT false,
	meta hstore,
    created_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now(),
    deleted_at timestamptz
)`,

		`CREATE TABLE formations (
    app_id uuid NOT NULL REFERENCES apps (app_id),
    release_id uuid NOT NULL REFERENCES releases (release_id),
    processes hstore,
    created_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now(),
    deleted_at timestamptz,
    PRIMARY KEY (app_id, release_id)
)`,

		`CREATE FUNCTION notify_formation() RETURNS TRIGGER AS $$
    BEGIN
        PERFORM pg_notify('formations', NEW.app_id || ':' || NEW.release_id);
        RETURN NULL;
    END;
$$ LANGUAGE plpgsql`,

		`CREATE TRIGGER notify_formation
    AFTER INSERT OR UPDATE ON formations
    FOR EACH ROW EXECUTE PROCEDURE notify_formation()`,

		`CREATE TABLE keys (
    key_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
    fingerprint text NOT NULL,
    key text NOT NULL,
    comment text,
    created_at timestamptz NOT NULL DEFAULT now(),
    deleted_at timestamptz
)`,
		`CREATE UNIQUE INDEX ON keys (fingerprint) WHERE deleted_at IS NULL`,

		`CREATE TABLE app_logs (
    app_id uuid NOT NULL REFERENCES apps (app_id),
    log_id bigint NOT NULL,
    event text NOT NULL,
    subject_id uuid,
    data text NOT NULL,
    created_at timestamptz NOT NULL DEFAULT now(),
    PRIMARY KEY (app_id, log_id)
)`,

		`CREATE TABLE app_log_ids (
    app_id uuid PRIMARY KEY REFERENCES apps (app_id),
    log_id bigint NOT NULL
)`,

		`CREATE FUNCTION next_log_id(uuid) RETURNS bigint AS $$
DECLARE
    in_app_id ALIAS FOR $1;
    next_log_id bigint;
BEGIN
    next_log_id := log_id FROM app_log_ids WHERE app_id = in_app_id FOR UPDATE;
    IF next_log_id IS NULL THEN
        next_log_id := 0;
        BEGIN
            INSERT INTO app_log_ids (app_id, log_id) VALUES (in_app_id, next_log_id+1);
            RETURN next_log_id;
        EXCEPTION WHEN unique_violation THEN
            next_log_id := log_id FROM app_log_ids WHERE app_id = in_app_id FOR UPDATE;
        END;
    END IF;

    UPDATE app_log_ids SET log_id = log_id+1 WHERE app_id = in_app_id;
    RETURN next_log_id;
END
$$ LANGUAGE plpgsql`,

		`CREATE TABLE providers (
    provider_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
    name text NOT NULL UNIQUE,
    url text NOT NULL UNIQUE,
    created_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now(),
    deleted_at timestamptz
)`,

		`CREATE TABLE resources (
    resource_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
    provider_id uuid NOT NULL REFERENCES providers (provider_id),
    external_id text NOT NULL,
    env hstore,
    created_at timestamptz NOT NULL DEFAULT now(),
    deleted_at timestamptz,
    UNIQUE (provider_id, external_id)
)`,

		`CREATE TABLE app_resources (
    app_id uuid NOT NULL REFERENCES apps (app_id),
    resource_id uuid NOT NULL REFERENCES resources (resource_id),
    created_at timestamptz NOT NULL DEFAULT now(),
    deleted_at timestamptz,
    PRIMARY KEY (app_id, resource_id)
)`,
		`CREATE INDEX ON app_resources (resource_id)`,
	)
	return m.Migrate(db)
}
<commit_msg>controller: Add jobs db table<commit_after>package main

import (
	"github.com/flynn/go-flynn/migrate"
	"github.com/flynn/go-sql"
)

func migrateDB(db *sql.DB) error {
	m := migrate.NewMigrations()
	m.Add(1,
		`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`,
		`CREATE EXTENSION IF NOT EXISTS "hstore"`,

		`CREATE TABLE artifacts (
    artifact_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
    type text NOT NULL,
    uri text NOT NULL,
    created_at timestamptz NOT NULL DEFAULT now(),
    deleted_at timestamptz,
    UNIQUE (type, uri)
)`,

		`CREATE TABLE releases (
    release_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
    artifact_id uuid NOT NULL REFERENCES artifacts (artifact_id),
    data text NOT NULL,
    created_at timestamptz NOT NULL DEFAULT now(),
    deleted_at timestamptz
)`,

		`CREATE TABLE apps (
    app_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
    name text UNIQUE NOT NULL,
    release_id uuid REFERENCES releases (release_id),
    protected bool NOT NULL DEFAULT false,
	meta hstore,
    created_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now(),
    deleted_at timestamptz
)`,

		`CREATE TABLE formations (
    app_id uuid NOT NULL REFERENCES apps (app_id),
    release_id uuid NOT NULL REFERENCES releases (release_id),
    processes hstore,
    created_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now(),
    deleted_at timestamptz,
    PRIMARY KEY (app_id, release_id)
)`,

		`CREATE FUNCTION notify_formation() RETURNS TRIGGER AS $$
    BEGIN
        PERFORM pg_notify('formations', NEW.app_id || ':' || NEW.release_id);
        RETURN NULL;
    END;
$$ LANGUAGE plpgsql`,

		`CREATE TRIGGER notify_formation
    AFTER INSERT OR UPDATE ON formations
    FOR EACH ROW EXECUTE PROCEDURE notify_formation()`,

		`CREATE TABLE keys (
    key_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
    fingerprint text NOT NULL,
    key text NOT NULL,
    comment text,
    created_at timestamptz NOT NULL DEFAULT now(),
    deleted_at timestamptz
)`,
		`CREATE UNIQUE INDEX ON keys (fingerprint) WHERE deleted_at IS NULL`,

		`CREATE TABLE app_logs (
    app_id uuid NOT NULL REFERENCES apps (app_id),
    log_id bigint NOT NULL,
    event text NOT NULL,
    subject_id uuid,
    data text NOT NULL,
    created_at timestamptz NOT NULL DEFAULT now(),
    PRIMARY KEY (app_id, log_id)
)`,

		`CREATE TABLE app_log_ids (
    app_id uuid PRIMARY KEY REFERENCES apps (app_id),
    log_id bigint NOT NULL
)`,

		`CREATE FUNCTION next_log_id(uuid) RETURNS bigint AS $$
DECLARE
    in_app_id ALIAS FOR $1;
    next_log_id bigint;
BEGIN
    next_log_id := log_id FROM app_log_ids WHERE app_id = in_app_id FOR UPDATE;
    IF next_log_id IS NULL THEN
        next_log_id := 0;
        BEGIN
            INSERT INTO app_log_ids (app_id, log_id) VALUES (in_app_id, next_log_id+1);
            RETURN next_log_id;
        EXCEPTION WHEN unique_violation THEN
            next_log_id := log_id FROM app_log_ids WHERE app_id = in_app_id FOR UPDATE;
        END;
    END IF;

    UPDATE app_log_ids SET log_id = log_id+1 WHERE app_id = in_app_id;
    RETURN next_log_id;
END
$$ LANGUAGE plpgsql`,

		`CREATE TABLE providers (
    provider_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
    name text NOT NULL UNIQUE,
    url text NOT NULL UNIQUE,
    created_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now(),
    deleted_at timestamptz
)`,

		`CREATE TABLE resources (
    resource_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
    provider_id uuid NOT NULL REFERENCES providers (provider_id),
    external_id text NOT NULL,
    env hstore,
    created_at timestamptz NOT NULL DEFAULT now(),
    deleted_at timestamptz,
    UNIQUE (provider_id, external_id)
)`,

		`CREATE TABLE app_resources (
    app_id uuid NOT NULL REFERENCES apps (app_id),
    resource_id uuid NOT NULL REFERENCES resources (resource_id),
    created_at timestamptz NOT NULL DEFAULT now(),
    deleted_at timestamptz,
    PRIMARY KEY (app_id, resource_id)
)`,
		`CREATE INDEX ON app_resources (resource_id)`,

		`CREATE TYPE job_state AS ENUM ('starting', 'up', 'down', 'crashed')`,
		`CREATE TABLE job_cache (
    job_id text NOT NULL,
    host_id text NOT NULL,
    app_id uuid NOT NULL REFERENCES apps (app_id),
    release_id uuid NOT NULL REFERENCES releases (release_id),
    process_type text NOT NULL,
    state job_state NOT NULL,
    created_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now(),
    PRIMARY KEY (job_id, host_id),
    FOREIGN KEY (app_id, release_id) REFERENCES formations (app_id, release_id)
)`,
	)
	return m.Migrate(db)
}
<|endoftext|>


<commit_before>package sqlr

import (
	"context"
	"reflect"

	"github.com/jjeffery/sqlr/private/column"
)

// Schema contains information about the database that is used
// when generating SQL statements.
//
// Information stored in the schema includes the SQL dialect,
// and the naming convention used to convert Go struct field names
// into database column names.
//
// Although the zero value schema can be used and represents a database schema
// with default values, it is more common to use the NewSchema function to
// create a schema with one or more options.
//
// A schema maintains an internal cache, which is used to store details of
// frequently called SQL commands for improved performance.
//
// A schema can be inexpensively cloned to provide a deep copy.
// This can occasionally be useful to define a common schema for a database,
// and then create copies to handle naming rules that are specific to a particular
// table, or a particular group of tables.
type Schema struct {
	dialect    Dialect
	convention NamingConvention
	cache      stmtCache
	fieldMap   *fieldMap
	identMap   *identMap
	key        string
}

// NewSchema creates a schema with options.
func NewSchema(opts ...SchemaOption) *Schema {
	schema := &Schema{}
	for _, opt := range opts {
		if opt != nil {
			opt(schema)
		}
	}
	return schema
}

// columnNamer returns an object that implements the columnNamer interface
// for the schema. The column namer returns the column name based on the
// list of field name/column name mappings for the schema, and the naming
// convention.
func (s *Schema) columnNamer() columnNamer {
	return columnNamerFunc(func(col *column.Info) string {
		if s.fieldMap != nil {
			if columnName, ok := s.fieldMap.lookup(col.FieldNames); ok {
				// If the field map returns an empty string, this means to
				// fallback to the naming convention. This provides a mechanism
				// to override any naming from a previous schema.
				if columnName != "" {
					return columnName
				}
			}
		}
		convention := s.convention
		if convention == nil {
			convention = defaultNamingConvention
		}
		return col.Path.ColumnName(convention, s.key)
	})
}

func (s *Schema) tableNamer() func(rowType reflect.Type) string {
	// TODO(jpj): this is pretty lame, does not lookup exceptions
	// or struct tag data. Just a basic implementation that mirrors
	// columnNamer, that will probably be refactored later.
	return func(rowType reflect.Type) string {
		convention := s.convention
		if convention == nil {
			convention = defaultNamingConvention
		}
		return convention.Convert(rowType.Name())
	}
}

// renameIdent implements the identRenamer interface.
func (s *Schema) renameIdent(ident string) (string, bool) {
	if s.identMap == nil {
		return "", false
	}
	return s.identMap.lookup(ident)
}

// getDialect returns the dialect for the schema. The aim is to make
// and empty Schema usable, so this method is necessary to ensure that
// a non-nil dialect is always available.
func (s *Schema) getDialect() Dialect {
	if s.dialect != nil {
		return s.dialect
	}
	return DefaultDialect
}

// Clone creates a copy of the schema, with options applied.
func (s *Schema) Clone(opts ...SchemaOption) *Schema {
	clone := &Schema{
		dialect:    s.dialect,
		convention: s.convention,
		fieldMap:   newFieldMap(s.fieldMap),
		identMap:   newIdentMap(s.identMap),
		key:        s.key,
	}
	for _, opt := range opts {
		opt(clone)
	}
	return clone
}

// Prepare creates a prepared statement for later queries or executions.
// Multiple queries or executions may be run concurrently from the returned
// statement.
func (s *Schema) Prepare(row interface{}, query string) (*Stmt, error) {
	// determine row type to use for statement
	rowType, err := inferRowType(row)
	if err != nil {
		return nil, err
	}

	// convert common shorthand SQL notations
	if query, err = checkSQL(query); err != nil {
		return nil, err
	}

	// attempt to get statement from the schema's statement cache
	stmt, ok := s.cache.lookup(rowType, query)
	if !ok {
		// build statement from scratch
		stmt, err = newStmt(s.getDialect(), s.columnNamer(), s, rowType, query)
		if err != nil {
			return nil, err
		}
		// add to schema's statement cache, returning the statement in the
		// cache -- this is just in case another goroutine has beaten us to it
		stmt = s.cache.set(rowType, query, stmt)
	}
	return stmt, nil
}

// Select executes a SELECT query and stores the result in rows.
//
// Deprecated: Use Session.Select instead.
//
// The argument passed to rows can be one of the following:
//  A pointer to an array of structs; or
//  a pointer to an array of struct pointers; or
//  a pointer to a struct.
// When rows is a pointer to an array it is populated with
// one item for each row returned by the SELECT query.
//
// When rows is a pointer to a struct, it is populated with
// the first row returned from the query. This is a good
// option when the query will only return one row.
//
// Select returns the number of rows returned by the SELECT
// query.
func (s *Schema) Select(db Querier, rows interface{}, sql string, args ...interface{}) (int, error) {
	stmt, err := s.Prepare(rows, sql)
	if err != nil {
		return 0, err
	}
	return stmt.Select(context.TODO(), db, rows, args...)
}

// Exec executes the query with the given row and optional arguments.
// It returns the number of rows affected by the statement.
//
// Deprecated: use Session.Exec instead.
//
// If the statement is an INSERT statement and the row has an auto-increment field,
// then the row is updated with the value of the auto-increment column, as long as
// the SQL driver supports this functionality.
func (s *Schema) Exec(db Querier, row interface{}, sql string, args ...interface{}) (int, error) {
	stmt, err := s.Prepare(row, sql)
	if err != nil {
		return 0, err
	}
	return stmt.Exec(context.TODO(), db, row, args...)
}

// Key returns the key associated with the schema.
func (s *Schema) Key() string {
	return s.key
}
<commit_msg>Use new TableName method in NamingConvention.<commit_after>package sqlr

import (
	"context"
	"reflect"

	"github.com/jjeffery/sqlr/private/column"
)

// Schema contains information about the database that is used
// when generating SQL statements.
//
// Information stored in the schema includes the SQL dialect,
// and the naming convention used to convert Go struct field names
// into database column names.
//
// Although the zero value schema can be used and represents a database schema
// with default values, it is more common to use the NewSchema function to
// create a schema with one or more options.
//
// A schema maintains an internal cache, which is used to store details of
// frequently called SQL commands for improved performance.
//
// A schema can be inexpensively cloned to provide a deep copy.
// This can occasionally be useful to define a common schema for a database,
// and then create copies to handle naming rules that are specific to a particular
// table, or a particular group of tables.
type Schema struct {
	dialect    Dialect
	convention NamingConvention
	cache      stmtCache
	fieldMap   *fieldMap
	identMap   *identMap
	key        string
}

// NewSchema creates a schema with options.
func NewSchema(opts ...SchemaOption) *Schema {
	schema := &Schema{}
	for _, opt := range opts {
		if opt != nil {
			opt(schema)
		}
	}
	return schema
}

// columnNamer returns an object that implements the columnNamer interface
// for the schema. The column namer returns the column name based on the
// list of field name/column name mappings for the schema, and the naming
// convention.
func (s *Schema) columnNamer() columnNamer {
	return columnNamerFunc(func(col *column.Info) string {
		if s.fieldMap != nil {
			if columnName, ok := s.fieldMap.lookup(col.FieldNames); ok {
				// If the field map returns an empty string, this means to
				// fallback to the naming convention. This provides a mechanism
				// to override any naming from a previous schema.
				if columnName != "" {
					return columnName
				}
			}
		}
		convention := s.convention
		if convention == nil {
			convention = defaultNamingConvention
		}
		return col.Path.ColumnName(convention, s.key)
	})
}

func (s *Schema) tableNamer() func(rowType reflect.Type) string {
	// TODO(jpj): this is pretty lame, does not lookup exceptions
	// or struct tag data. Just a basic implementation that mirrors
	// columnNamer, that will probably be refactored later.
	return func(rowType reflect.Type) string {
		convention := s.convention
		if convention == nil {
			convention = defaultNamingConvention
		}
		return convention.TableName(rowType.Name())
	}
}

// renameIdent implements the identRenamer interface.
func (s *Schema) renameIdent(ident string) (string, bool) {
	if s.identMap == nil {
		return "", false
	}
	return s.identMap.lookup(ident)
}

// getDialect returns the dialect for the schema. The aim is to make
// and empty Schema usable, so this method is necessary to ensure that
// a non-nil dialect is always available.
func (s *Schema) getDialect() Dialect {
	if s.dialect != nil {
		return s.dialect
	}
	return DefaultDialect
}

// Clone creates a copy of the schema, with options applied.
func (s *Schema) Clone(opts ...SchemaOption) *Schema {
	clone := &Schema{
		dialect:    s.dialect,
		convention: s.convention,
		fieldMap:   newFieldMap(s.fieldMap),
		identMap:   newIdentMap(s.identMap),
		key:        s.key,
	}
	for _, opt := range opts {
		opt(clone)
	}
	return clone
}

// Prepare creates a prepared statement for later queries or executions.
// Multiple queries or executions may be run concurrently from the returned
// statement.
func (s *Schema) Prepare(row interface{}, query string) (*Stmt, error) {
	// determine row type to use for statement
	rowType, err := inferRowType(row)
	if err != nil {
		return nil, err
	}

	// convert common shorthand SQL notations
	if query, err = checkSQL(query); err != nil {
		return nil, err
	}

	// attempt to get statement from the schema's statement cache
	stmt, ok := s.cache.lookup(rowType, query)
	if !ok {
		// build statement from scratch
		stmt, err = newStmt(s.getDialect(), s.columnNamer(), s, rowType, query)
		if err != nil {
			return nil, err
		}
		// add to schema's statement cache, returning the statement in the
		// cache -- this is just in case another goroutine has beaten us to it
		stmt = s.cache.set(rowType, query, stmt)
	}
	return stmt, nil
}

// Select executes a SELECT query and stores the result in rows.
//
// Deprecated: Use Session.Select instead.
//
// The argument passed to rows can be one of the following:
//  A pointer to an array of structs; or
//  a pointer to an array of struct pointers; or
//  a pointer to a struct.
// When rows is a pointer to an array it is populated with
// one item for each row returned by the SELECT query.
//
// When rows is a pointer to a struct, it is populated with
// the first row returned from the query. This is a good
// option when the query will only return one row.
//
// Select returns the number of rows returned by the SELECT
// query.
func (s *Schema) Select(db Querier, rows interface{}, sql string, args ...interface{}) (int, error) {
	stmt, err := s.Prepare(rows, sql)
	if err != nil {
		return 0, err
	}
	return stmt.Select(context.TODO(), db, rows, args...)
}

// Exec executes the query with the given row and optional arguments.
// It returns the number of rows affected by the statement.
//
// Deprecated: use Session.Exec instead.
//
// If the statement is an INSERT statement and the row has an auto-increment field,
// then the row is updated with the value of the auto-increment column, as long as
// the SQL driver supports this functionality.
func (s *Schema) Exec(db Querier, row interface{}, sql string, args ...interface{}) (int, error) {
	stmt, err := s.Prepare(row, sql)
	if err != nil {
		return 0, err
	}
	return stmt.Exec(context.TODO(), db, row, args...)
}

// Key returns the key associated with the schema.
func (s *Schema) Key() string {
	return s.key
}
<|endoftext|>


<commit_before>package waveguide

import (
	"fmt"
	"html/template"
	"regexp"
	"strings"
	"time"

	"golang.org/x/net/context"
	"google.golang.org/appengine/datastore"
)

type Spot struct {
	Name string
	// TODO: Change to using a general url to be less coupled
	// to a particular site.
	MswPath string
	// Surf conditions at this spot, most recent last
	Qual []Quality
}

type Quality struct {
	// How many stars out of five
	Rating     int
	WaveHeight string
	TimeUnix   int64
}

func SpotKey(ctx context.Context, mswPath string) *datastore.Key {
	return datastore.NewKey(ctx, "Spot", mswPath, 0, nil)
}

// AddQualityToSpot adds a surfing condition quality to the spot with the given surf report path.
// If there is no such spot in datastore, AddQualityToSpot creates one.
func AddQualityToSpot(ctx context.Context, path string, qual *Quality) error {
	var s Spot
	key := SpotKey(ctx, path)
	err := datastore.Get(ctx, key, &s)
	if err == datastore.ErrNoSuchEntity {
		// This spot isn't yet in the db. That's okay, we'll just fill in the blanks and add it with the
		// quality.
		s.Name, err = surfReportPathToName(path)
		if err != nil {
			return fmt.Errorf("AddQualityToSpot: %v", err)
		}
		s.MswPath = path
	} else if err != nil {
		return err
	}
	s.Qual = append(s.Qual, *qual)
	for len(s.Qual) > maxQualitiesPerSpot {
		drop := len(s.Qual) - maxQualitiesPerSpot
		s.Qual = s.Qual[drop:len(s.Qual)]
	}
	_, err = datastore.Put(ctx, key, &s)
	if err != nil {
		return err
	}
	return nil
}

func (s *Spot) HTMLName() template.HTML {
	return template.HTML(s.Name)
}

func (s *Spot) LatestQuality() *Quality {
	if len(s.Qual) == 0 {
		return nil
	}
	return &s.Qual[len(s.Qual)-1]
}

func (s *Spot) ReportURL() string {
	return "http://magicseaweed.com" + s.MswPath
}

func (s *Spot) MapURL() string {
	return strings.Replace(s.ReportURL(), "Report", "Guide", 1)
}

func (q *Quality) Stars() string {
	runes := make([]rune, 0, 5)
	for i := 0; i < q.Rating; i++ {
		runes = append(runes, '★')
	}
	for i := 0; i < 5-q.Rating; i++ {
		runes = append(runes, '☆')
	}
	return string(runes)
}

var firstAlphaRx = regexp.MustCompile(`([a-z]+).*`)
var decimalRx = regexp.MustCompile(`\.\d+`)

// HowLong returns a string telling how long it has been since the time given by its TimeUnix field.
func (q *Quality) HowLong() string {
	t := time.Unix(q.TimeUnix, 0)
	dt := time.Now().Sub(t)
	s := dt.String()
	s = firstAlphaRx.ReplaceAllString(s, "$1")
	s = decimalRx.ReplaceAllString(s, "")
	return s
}

type ByRating []Spot

func (r ByRating) Len() int      { return len(r) }
func (r ByRating) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r ByRating) Less(i, j int) bool {
	qi := r[i].LatestQuality()
	qj := r[j].LatestQuality()
	if qi == nil {
		if qj == nil {
			return true
		} else {
			return false
		}
	} else {
		if qj == nil {
			return true
		} else {
			return qi.Rating > qj.Rating
		}
	}
}
<commit_msg>Sort by name for spots with equal ratings.<commit_after>package waveguide

import (
	"fmt"
	"html/template"
	"regexp"
	"strings"
	"time"

	"golang.org/x/net/context"
	"google.golang.org/appengine/datastore"
)

type Spot struct {
	Name string
	// TODO: Change to using a general url to be less coupled
	// to a particular site.
	MswPath string
	// Surf conditions at this spot, most recent last
	Qual []Quality
}

type Quality struct {
	// How many stars out of five
	Rating     int
	WaveHeight string
	TimeUnix   int64
}

func SpotKey(ctx context.Context, mswPath string) *datastore.Key {
	return datastore.NewKey(ctx, "Spot", mswPath, 0, nil)
}

// AddQualityToSpot adds a surfing condition quality to the spot with the given surf report path.
// If there is no such spot in datastore, AddQualityToSpot creates one.
func AddQualityToSpot(ctx context.Context, path string, qual *Quality) error {
	var s Spot
	key := SpotKey(ctx, path)
	err := datastore.Get(ctx, key, &s)
	if err == datastore.ErrNoSuchEntity {
		// This spot isn't yet in the db. That's okay, we'll just fill in the blanks and add it with the
		// quality.
		s.Name, err = surfReportPathToName(path)
		if err != nil {
			return fmt.Errorf("AddQualityToSpot: %v", err)
		}
		s.MswPath = path
	} else if err != nil {
		return err
	}
	s.Qual = append(s.Qual, *qual)
	for len(s.Qual) > maxQualitiesPerSpot {
		drop := len(s.Qual) - maxQualitiesPerSpot
		s.Qual = s.Qual[drop:len(s.Qual)]
	}
	_, err = datastore.Put(ctx, key, &s)
	if err != nil {
		return err
	}
	return nil
}

func (s *Spot) HTMLName() template.HTML {
	return template.HTML(s.Name)
}

func (s *Spot) LatestQuality() *Quality {
	if len(s.Qual) == 0 {
		return nil
	}
	return &s.Qual[len(s.Qual)-1]
}

func (s *Spot) ReportURL() string {
	return "http://magicseaweed.com" + s.MswPath
}

func (s *Spot) MapURL() string {
	return strings.Replace(s.ReportURL(), "Report", "Guide", 1)
}

func (q *Quality) Stars() string {
	runes := make([]rune, 0, 5)
	for i := 0; i < q.Rating; i++ {
		runes = append(runes, '★')
	}
	for i := 0; i < 5-q.Rating; i++ {
		runes = append(runes, '☆')
	}
	return string(runes)
}

var firstAlphaRx = regexp.MustCompile(`([a-z]+).*`)
var decimalRx = regexp.MustCompile(`\.\d+`)

// HowLong returns a string telling how long it has been since the time given by its TimeUnix field.
func (q *Quality) HowLong() string {
	t := time.Unix(q.TimeUnix, 0)
	dt := time.Now().Sub(t)
	s := dt.String()
	s = firstAlphaRx.ReplaceAllString(s, "$1")
	s = decimalRx.ReplaceAllString(s, "")
	return s
}

// RatingExt gives the rating for the quality, or -1 if it is nil.
// This is a helper for sorting.
func (q *Quality) RatingExt() int {
	if q == nil {
		return -1
	}
	return q.Rating
}

type ByRating []Spot

func (r ByRating) Len() int      { return len(r) }
func (r ByRating) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r ByRating) Less(i, j int) bool {
	rati := r[i].LatestQuality().RatingExt()
	ratj := r[j].LatestQuality().RatingExt()
	return rati > ratj || (rati == ratj && r[i].Name < r[j].Name)
}
<|endoftext|>


<commit_before>/* {{{ Copyright (c) Paul R. Tagliamonte <paultag@debian.org>, 2015
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE. }}} */

package archive

import (
	"fmt"
	"io"
	"os"
	"strconv"

	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"hash"

	"pault.ag/go/debian/control"
	"pault.ag/go/debian/deb"
	"pault.ag/go/debian/dependency"
	"pault.ag/go/debian/version"
)

// Package {{{

// Binary .deb Package entry, as it exists in the Packages file, which
// contains the .deb Control information, as well as information on
// where the file lives, the file size, and some hashes.
type Package struct {
	control.Paragraph

	Package       string `required:"true"`
	Source        string
	Version       version.Version `required:"true"`
	Section       string
	Priority      string
	Architecture  dependency.Arch `required:"true"`
	Essential     string
	InstalledSize int    `control:"Installed-Size"`
	Maintainer    string `required:"true"`
	Description   string `required:"true"`
	Homepage      string

	Filename       string `required:"true"`
	Size           int    `required:"true"`
	MD5sum         string
	SHA1           string
	SHA256         string
	SHA512         string
	DescriptionMD5 string `control:"Description-md5"`
}

// Depends helpers {{{

// Get a dependency relation on the fly, given a key. This is just to reduce
// redundency on the methods below.
func getDepends(paragraph control.Paragraph, key string) (*dependency.Dependency, error) {
	if val, ok := paragraph.Values[key]; ok {
		return dependency.Parse(val)
	}
	return &dependency.Dependency{}, nil
}

// Parse the Depends Dependency relation on this package.
func (p Package) Depends() (*dependency.Dependency, error) {
	return getDepends(p.Paragraph, "Depends")
}

// Parse the Depends Suggests relation on this package.
func (p Package) Suggests() (*dependency.Dependency, error) {
	return getDepends(p.Paragraph, "Suggest")
}

// Parse the Depends Built-Using relation on this package.
func (p Package) BuiltUsing() (*dependency.Dependency, error) {
	return getDepends(p.Paragraph, "Built-Using")
}

// Parse the Depends Breaks relation on this package.
func (p Package) Breaks() (*dependency.Dependency, error) {
	return getDepends(p.Paragraph, "Breaks")
}

// Parse the Depends Replaces relation on this package.
func (p Package) Replaces() (*dependency.Dependency, error) {
	return getDepends(p.Paragraph, "Replaces")
}

// Parse the Depends Pre-Depends relation on this package.
func (p Package) PreDepends() (*dependency.Dependency, error) {
	return getDepends(p.Paragraph, "Pre-Depends")
}

// }}}

// PackageFromDeb {{{

// Create a Package entry from a deb.Deb file. This will copy the binary
// .deb Control file into the Package entry, and set information as to
// the location of the file, the size of the file, and hash the file.
func PackageFromDeb(debFile deb.Deb) (*Package, error) {
	pkg := Package{}

	paragraph := debFile.Control.Paragraph
	paragraph.Set("Filename", debFile.Path)
	/* Now, let's do some magic */

	fd, err := os.Open(debFile.Path)
	if err != nil {
		return nil, err
	}
	stat, err := fd.Stat()
	if err != nil {
		return nil, err
	}

	paragraph.Set("Size", strconv.Itoa(int(stat.Size())))
	/* Right, now, in addition, we ought to hash the crap out of the file */

	md5sum := md5.New()
	sha1 := sha1.New()
	sha256 := sha256.New()

	writer := io.MultiWriter(md5sum, sha256, sha1)

	if _, err := io.Copy(writer, fd); err != nil {
		return nil, err
	}

	for key, hasher := range map[string]hash.Hash{
		"MD5sum": md5sum,
		"SHA1":   sha1,
		"SHA256": sha256,
	} {
		paragraph.Set(key, fmt.Sprintf("%x", hasher.Sum(nil)))
	}

	return &pkg, control.UnpackFromParagraph(debFile.Control.Paragraph, &pkg)
}

// }}}

// }}}

// Packages {{{

// Iterator to access the entries contained in the Packages entry in an
// apt repo. This contians information about the binary Debian packages.
type Packages struct {
	decoder *control.Decoder
}

// Next {{{

// Get the next Package entry in the Packages list. This will return an
// io.EOF at the last entry.
func (p *Packages) Next() (*Package, error) {
	next := Package{}
	return &next, p.decoder.Decode(&next)
}

// }}}

// LoadPackagesFile {{{

// Given a path, create a Packages iterator. Note that the Packages
// file is not OpenPGP signed, so one will need to verify the integrety
// of this file from the InRelease file before trusting any output.
func LoadPackagesFile(path string) (*Packages, error) {
	fd, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	return LoadPackages(fd)
}

// }}}

// LoadPackages {{{

// Given an io.Reader, create a Packages iterator. Note that the Packages
// file is not OpenPGP signed, so one will need to verify the integrety
// of this file from the InRelease file before trusting any output.
func LoadPackages(in io.Reader) (*Packages, error) {
	decoder, err := control.NewDecoder(in, nil)
	if err != nil {
		return nil, err
	}
	return &Packages{decoder: decoder}, nil
}

// }}}

// }}}

// vim: foldmethod=marker
<commit_msg>that was a dumb idea<commit_after>/* {{{ Copyright (c) Paul R. Tagliamonte <paultag@debian.org>, 2015
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE. }}} */

package archive

import (
	"fmt"
	"io"
	"os"
	"strconv"

	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"hash"

	"pault.ag/go/debian/control"
	"pault.ag/go/debian/deb"
	"pault.ag/go/debian/dependency"
	"pault.ag/go/debian/version"
)

// Package {{{

// Binary .deb Package entry, as it exists in the Packages file, which
// contains the .deb Control information, as well as information on
// where the file lives, the file size, and some hashes.
type Package struct {
	control.Paragraph

	Package       string `required:"true"`
	Source        string
	Version       version.Version `required:"true"`
	Section       string
	Priority      string
	Architecture  dependency.Arch `required:"true"`
	Essential     string
	InstalledSize int    `control:"Installed-Size"`
	Maintainer    string `required:"true"`
	Description   string `required:"true"`
	Homepage      string

	Filename       string `required:"true"`
	Size           int    `required:"true"`
	MD5sum         string
	SHA1           string
	SHA256         string
	SHA512         string
	DescriptionMD5 string `control:"Description-md5"`

	Depends    dependency.Dependency
	Suggests   dependency.Dependency
	BuiltUsing dependency.Dependency `control:"Built-Using"`
	Breaks     dependency.Dependency
	PreDepends dependency.Dependency `control:"Pre-Depends"`
}

// PackageFromDeb {{{

// Create a Package entry from a deb.Deb file. This will copy the binary
// .deb Control file into the Package entry, and set information as to
// the location of the file, the size of the file, and hash the file.
func PackageFromDeb(debFile deb.Deb) (*Package, error) {
	pkg := Package{}

	paragraph := debFile.Control.Paragraph
	paragraph.Set("Filename", debFile.Path)
	/* Now, let's do some magic */

	fd, err := os.Open(debFile.Path)
	if err != nil {
		return nil, err
	}
	stat, err := fd.Stat()
	if err != nil {
		return nil, err
	}

	paragraph.Set("Size", strconv.Itoa(int(stat.Size())))
	/* Right, now, in addition, we ought to hash the crap out of the file */

	md5sum := md5.New()
	sha1 := sha1.New()
	sha256 := sha256.New()

	writer := io.MultiWriter(md5sum, sha256, sha1)

	if _, err := io.Copy(writer, fd); err != nil {
		return nil, err
	}

	for key, hasher := range map[string]hash.Hash{
		"MD5sum": md5sum,
		"SHA1":   sha1,
		"SHA256": sha256,
	} {
		paragraph.Set(key, fmt.Sprintf("%x", hasher.Sum(nil)))
	}

	return &pkg, control.UnpackFromParagraph(debFile.Control.Paragraph, &pkg)
}

// }}}

// }}}

// Packages {{{

// Iterator to access the entries contained in the Packages entry in an
// apt repo. This contians information about the binary Debian packages.
type Packages struct {
	decoder *control.Decoder
}

// Next {{{

// Get the next Package entry in the Packages list. This will return an
// io.EOF at the last entry.
func (p *Packages) Next() (*Package, error) {
	next := Package{}
	return &next, p.decoder.Decode(&next)
}

// }}}

// LoadPackagesFile {{{

// Given a path, create a Packages iterator. Note that the Packages
// file is not OpenPGP signed, so one will need to verify the integrety
// of this file from the InRelease file before trusting any output.
func LoadPackagesFile(path string) (*Packages, error) {
	fd, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	return LoadPackages(fd)
}

// }}}

// LoadPackages {{{

// Given an io.Reader, create a Packages iterator. Note that the Packages
// file is not OpenPGP signed, so one will need to verify the integrety
// of this file from the InRelease file before trusting any output.
func LoadPackages(in io.Reader) (*Packages, error) {
	decoder, err := control.NewDecoder(in, nil)
	if err != nil {
		return nil, err
	}
	return &Packages{decoder: decoder}, nil
}

// }}}

// }}}

// vim: foldmethod=marker
<|endoftext|>


<commit_before>package paginate

import (
	"net/url"
	"strconv"
)

const (
	DESC = 0
	ASC  = 1
)

type Cursor struct {
	Value     string
	Offset    int
	Count     int
	Order     string
	Direction int
	Url       *url.URL
}

func NewCursorFromDefaults(c *Cursor) *Cursor {
	if c == nil {
		c = &Cursor{}
	}
	if c.Count == 0 {
		c.Count = 10
	}
	return c
}

type Item interface {
	PaginationValue(c *Cursor) string
}

type Response struct {
	Next *string `json:"next"`
}

func NewCursorFromUrl(u *url.URL, defaults *Cursor) *Cursor {
	c := NewCursorFromDefaults(defaults)
	c.Url = u

	values := u.Query()
	if v, ok := values["value"]; ok {
		c.Value = v[0]
	}
	if v, ok := values["offset"]; ok {
		offset, err := strconv.Atoi(v[0])
		if err != nil {
			c.Offset = offset
		}
	}
	if v, ok := values["count"]; ok {
		count, err := strconv.Atoi(v[0])
		if err == nil {
			c.Count = count
		}
	}
	if v, ok := values["order"]; ok {
		c.Order = v[0]
	}
	if v, ok := values["direction"]; ok {
		direction, err := strconv.Atoi(v[0])
		if err == nil && (direction == ASC || direction == DESC) {
			c.Direction = direction
		}
	}
	return c
}

func (c *Cursor) lastItemIndex(items []Item) int {
	if len(items) <= c.Count {
		return len(items) - 1
	} else {
		// This is the index of the first item on the next page.
		return c.Count
	}
}

func (c *Cursor) equalCount(items []Item, lastItemIndex int) int {
	count := 0
	for i := 0; i < c.Count; i++ {
		if items[i].PaginationValue(c) == items[lastItemIndex].PaginationValue(c) {
			count += 1
		}
	}
	return count
}

func (c *Cursor) after(items []Item, last, direction int) *Cursor {
	if len(items) == 0 {
		return nil
	}
	value := items[last].PaginationValue(c)
	offset := c.equalCount(items, last)
	if offset == c.Count && value == c.Value {
		offset += c.Offset
	}
	return &Cursor{value, offset, c.Count, c.Order, direction, c.Url}
}

func (c *Cursor) Prev(items []Item) *Cursor {
	lastItemIndex := 0
	var newDirection int
	if c.Direction == ASC {
		newDirection = DESC
	} else {
		newDirection = ASC
	}
	return c.after(items, lastItemIndex, newDirection)
}

func (c *Cursor) Next(items []Item, nextPagePrefetched bool) *Cursor {
	if nextPagePrefetched && len(items) <= c.Count {
		return nil
	}
	return c.after(items, c.lastItemIndex(items), c.Direction)
}

func (c *Cursor) ToUrl() *url.URL {
	newUrl := *c.Url
	values := newUrl.Query()
	values.Set("value", c.Value)
	values.Set("offset", strconv.Itoa(c.Offset))
	values.Set("count", strconv.Itoa(c.Count))
	values.Set("order", c.Order)
	values.Set("direction", strconv.Itoa(c.Direction))
	newUrl.RawQuery = values.Encode()
	return &newUrl
}

func (c *Cursor) ToResponse(items []Item, nextPagePrefetched bool) *Response {
	response := &Response{}
	next := c.Next(items, nextPagePrefetched)
	if next != nil {
		nextUrlString := next.ToUrl().String()
		response.Next = &nextUrlString
	}
	return response
}
<commit_msg>Add DEFAULT_COUNT constant<commit_after>package paginate

import (
	"net/url"
	"strconv"
)

const (
	DESC = 0
	ASC  = 1

	DEFAULT_COUNT = 10
)

type Cursor struct {
	Value     string
	Offset    int
	Count     int
	Order     string
	Direction int
	Url       *url.URL
}

func NewCursorFromDefaults(c *Cursor) *Cursor {
	if c == nil {
		c = &Cursor{}
	}
	if c.Count == 0 {
		c.Count = DEFAULT_COUNT
	}
	return c
}

type Item interface {
	PaginationValue(c *Cursor) string
}

type Response struct {
	Next *string `json:"next"`
}

func NewCursorFromUrl(u *url.URL, defaults *Cursor) *Cursor {
	c := NewCursorFromDefaults(defaults)
	c.Url = u

	values := u.Query()
	if v, ok := values["value"]; ok {
		c.Value = v[0]
	}
	if v, ok := values["offset"]; ok {
		offset, err := strconv.Atoi(v[0])
		if err != nil {
			c.Offset = offset
		}
	}
	if v, ok := values["count"]; ok {
		count, err := strconv.Atoi(v[0])
		if err == nil {
			c.Count = count
		}
	}
	if v, ok := values["order"]; ok {
		c.Order = v[0]
	}
	if v, ok := values["direction"]; ok {
		direction, err := strconv.Atoi(v[0])
		if err == nil && (direction == ASC || direction == DESC) {
			c.Direction = direction
		}
	}
	return c
}

func (c *Cursor) lastItemIndex(items []Item) int {
	if len(items) <= c.Count {
		return len(items) - 1
	} else {
		// This is the index of the first item on the next page.
		return c.Count
	}
}

func (c *Cursor) equalCount(items []Item, lastItemIndex int) int {
	count := 0
	for i := 0; i < c.Count; i++ {
		if items[i].PaginationValue(c) == items[lastItemIndex].PaginationValue(c) {
			count += 1
		}
	}
	return count
}

func (c *Cursor) after(items []Item, last, direction int) *Cursor {
	if len(items) == 0 {
		return nil
	}
	value := items[last].PaginationValue(c)
	offset := c.equalCount(items, last)
	if offset == c.Count && value == c.Value {
		offset += c.Offset
	}
	return &Cursor{value, offset, c.Count, c.Order, direction, c.Url}
}

func (c *Cursor) Prev(items []Item) *Cursor {
	lastItemIndex := 0
	var newDirection int
	if c.Direction == ASC {
		newDirection = DESC
	} else {
		newDirection = ASC
	}
	return c.after(items, lastItemIndex, newDirection)
}

func (c *Cursor) Next(items []Item, nextPagePrefetched bool) *Cursor {
	if nextPagePrefetched && len(items) <= c.Count {
		return nil
	}
	return c.after(items, c.lastItemIndex(items), c.Direction)
}

func (c *Cursor) ToUrl() *url.URL {
	newUrl := *c.Url
	values := newUrl.Query()
	values.Set("value", c.Value)
	values.Set("offset", strconv.Itoa(c.Offset))
	values.Set("count", strconv.Itoa(c.Count))
	values.Set("order", c.Order)
	values.Set("direction", strconv.Itoa(c.Direction))
	newUrl.RawQuery = values.Encode()
	return &newUrl
}

func (c *Cursor) ToResponse(items []Item, nextPagePrefetched bool) *Response {
	response := &Response{}
	next := c.Next(items, nextPagePrefetched)
	if next != nil {
		nextUrlString := next.ToUrl().String()
		response.Next = &nextUrlString
	}
	return response
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/url"
)

const (
	ASC  = 1
	DESC = -1
)

type Item interface {
	PaginationValue(key string) interface{}
}

type Interface interface {
	Equal(order string, i, j int) bool
	Value(order string, i int) interface{}
	Len() int
}

type Page struct {
	items []Item
}

func (p *Page) Equal(order string, i, j int) bool {
	return p.Value(order, i) == p.Value(order, j)
}

func (p *Page) Value(order string, i int) interface{} {
	return p.items[i].PaginationValue(order)
}

func (p *Page) Len() int {
	return len(p.items)
}

type Config struct {
	pageSize  int
	order     string
	direction int
}

type Cursor struct {
	Value     interface{}
	Offset    int
	Order     string
	Direction int
}

type Pagination struct {
	Cursor
	config Config
}

func (p *Pagination) max(items Interface) int {
	if items.Len() <= p.config.pageSize {
		return items.Len() - 1
	} else {
		return p.config.pageSize
	}
}

func (p *Pagination) equalCount(items Interface, order string, max int) int {
	c := 0
	for i := 0; i < p.config.pageSize; i++ {
		if items.Equal(order, i, max) {
			c += 1
		}
	}
	return c
}

func NewPagination(cursor Cursor, config Config) *Pagination {
	if cursor.Order == "" {
		cursor.Order = config.order
	}
	if cursor.Direction == 0 {
		cursor.Direction = config.direction
	}
	return &Pagination{cursor, config}
}

func NewPaginationFromUrl(u *url.URL, c Config) Pagination {
	return Pagination{config: c}
}

func (p *Pagination) next(items Interface) *Pagination {
	max := p.max(items)
	value := items.Value(p.Order, max)
	offset := p.equalCount(items, p.Order, max)

	if offset == p.config.pageSize && value == p.Value {
		offset += p.Offset
	}

	cursor := Cursor{value, offset, p.Order, p.Direction}
	return &Pagination{cursor, p.config}
}

type Comment struct {
	text       string
	created_at int
	updated_at int
}

func (c *Comment) PaginationValue(key string) interface{} {
	switch {
	case key == "created_at":
		return c.created_at
	case key == "updated_at":
		return c.updated_at
	default:
		return nil
	}
}

func main() {
	cursor := Cursor{Order: "created_at"}
	config := Config{pageSize: 2, order: "updated_at", direction: DESC}
	pagination := NewPagination(cursor, config)

	items := &Page{[]Item{
		&Comment{"a", 0, 4},
		&Comment{"b", 1, 4},
		&Comment{"c", 2, 5},
		&Comment{"d", 3, 5},
		&Comment{"e", 3, 5},
	}}
	next := pagination.next(items)

	fmt.Printf("next value: %i, offset: %i\n", next.Value, next.Offset)
}
<commit_msg>Add Pagination.prev<commit_after>package main

import (
	"fmt"
	"net/url"
)

const (
	ASC  = 1
	DESC = -1
)

type Item interface {
	PaginationValue(key string) interface{}
}

type Interface interface {
	Equal(order string, i, j int) bool
	Value(order string, i int) interface{}
	Len() int
}

type Page struct {
	items []Item
}

func (p *Page) Equal(order string, i, j int) bool {
	return p.Value(order, i) == p.Value(order, j)
}

func (p *Page) Value(order string, i int) interface{} {
	return p.items[i].PaginationValue(order)
}

func (p *Page) Len() int {
	return len(p.items)
}

type Config struct {
	pageSize  int
	order     string
	direction int
}

type Cursor struct {
	Value     interface{}
	Offset    int
	Order     string
	Direction int
}

type Pagination struct {
	Cursor
	config Config
}

func (p *Pagination) max(items Interface) int {
	if items.Len() <= p.config.pageSize {
		return items.Len() - 1
	} else {
		return p.config.pageSize
	}
}

func (p *Pagination) equalCount(items Interface, order string, max int) int {
	c := 0
	for i := 0; i < p.config.pageSize; i++ {
		if items.Equal(order, i, max) {
			c += 1
		}
	}
	return c
}

func NewPagination(cursor Cursor, config Config) *Pagination {
	if cursor.Order == "" {
		cursor.Order = config.order
	}
	if cursor.Direction == 0 {
		cursor.Direction = config.direction
	}
	return &Pagination{cursor, config}
}

func NewPaginationFromUrl(u *url.URL, c Config) Pagination {
	return Pagination{config: c}
}

func (p *Pagination) after(items Interface, last, direction int) *Pagination {
	value := items.Value(p.Order, last)
	offset := p.equalCount(items, p.Order, last)

	if offset == p.config.pageSize && value == p.Value {
		offset += p.Offset
	}

	cursor := Cursor{value, offset, p.Order, direction}
	return &Pagination{cursor, p.config}
}

func (p *Pagination) prev(items Interface) *Pagination {
	min := 0
	return p.after(items, min, p.Direction*-1)
}

func (p *Pagination) next(items Interface) *Pagination {
	max := p.max(items)
	return p.after(items, max, p.Direction)
}

type Comment struct {
	text       string
	created_at int
	updated_at int
}

func (c *Comment) PaginationValue(key string) interface{} {
	switch {
	case key == "created_at":
		return c.created_at
	case key == "updated_at":
		return c.updated_at
	default:
		return nil
	}
}

func main() {
	cursor := Cursor{Order: "created_at"}
	config := Config{pageSize: 2, order: "updated_at", direction: DESC}
	pagination := NewPagination(cursor, config)

	items := &Page{[]Item{
		&Comment{"e", 3, 5},
		&Comment{"d", 3, 5},
		&Comment{"c", 2, 5},
		&Comment{"b", 1, 4},
		&Comment{"a", 0, 4},
	}}
	next := pagination.next(items)
	prev := pagination.prev(items)

	fmt.Printf("next value: %i, offset: %i, direction: %i\n", next.Value, next.Offset, next.Direction)
	fmt.Printf("prev value: %i, offset: %i, direction: %i\n", prev.Value, prev.Offset, prev.Direction)
}
<|endoftext|>


<commit_before>// Copyright 2016 Google Inc. All Rights Reserved.
//
// 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 pubsub

import (
	"errors"
	"fmt"
	"math"
	"runtime"
	"strings"
	"sync"
	"time"

	"cloud.google.com/go/iam"
	"github.com/golang/protobuf/proto"
	"golang.org/x/net/context"
	"google.golang.org/api/support/bundler"
	pb "google.golang.org/genproto/googleapis/pubsub/v1"
)

const (
	// The maximum number of messages that can be in a single publish request, as
	// determined by the PubSub service.
	MaxPublishRequestCount = 1000

	// The maximum size of a single publish request in bytes, as determined by the PubSub service.
	MaxPublishRequestBytes = 1e7
)

// ErrOversizedMessage indicates that a message's size exceeds MaxPublishRequestBytes.
var ErrOversizedMessage = bundler.ErrOversizedItem

// Topic is a reference to a PubSub topic.
type Topic struct {
	s service
	// The fully qualified identifier for the topic, in the format "projects/<projid>/topics/<name>"
	name string

	// Settings for publishing messages. All changes must be made before the
	// first call to Publish. The default is DefaultPublishSettings.
	PublishSettings PublishSettings

	mu      sync.RWMutex
	stopped bool
	bundler *bundler.Bundler

	wg sync.WaitGroup

	// Channel for message bundles to be published. Close to indicate that Stop was called.
	bundlec chan []*bundledMessage
}

// PublishSettings control the bundling of published messages.
type PublishSettings struct {

	// Publish a non-empty batch after this delay has passed.
	DelayThreshold time.Duration

	// Publish a batch when it has this many messages. The maximum is
	// MaxPublishRequestCount.
	CountThreshold int

	// Publish a batch when its size in bytes reaches this value.
	ByteThreshold int

	// The number of goroutines that invoke the Publish RPC concurrently.
	// Defaults to a multiple of GOMAXPROCS.
	NumGoroutines int

	// The maximum time that the client will attempt to publish a bundle of messages.
	Timeout time.Duration
}

// DefaultPublishSettings holds the default values for topics' PublishSettings.
var DefaultPublishSettings = PublishSettings{
	DelayThreshold: 1 * time.Millisecond,
	CountThreshold: 100,
	ByteThreshold:  1e6,
	Timeout:        60 * time.Second,
}

// CreateTopic creates a new topic.
// The specified topic ID must start with a letter, and contain only letters
// ([A-Za-z]), numbers ([0-9]), dashes (-), underscores (_), periods (.),
// tildes (~), plus (+) or percent signs (%). It must be between 3 and 255
// characters in length, and must not start with "goog".
// If the topic already exists an error will be returned.
func (c *Client) CreateTopic(ctx context.Context, id string) (*Topic, error) {
	t := c.Topic(id)
	err := c.s.createTopic(ctx, t.name)
	return t, err
}

// Topic creates a reference to a topic.
//
// If a Topic's Publish method is called, it has background goroutines
// associated with it. Clean them up by calling Topic.Stop.
//
// Avoid creating many Topic instances if you use them to publish.
func (c *Client) Topic(id string) *Topic {
	return newTopic(c.s, fmt.Sprintf("projects/%s/topics/%s", c.projectID, id))
}

func newTopic(s service, name string) *Topic {
	// bundlec is unbuffered. A buffer would occupy memory not
	// accounted for by the bundler, so BufferedByteLimit would be a lie:
	// the actual memory consumed would be higher.
	return &Topic{
		s:               s,
		name:            name,
		PublishSettings: DefaultPublishSettings,
		bundlec:         make(chan []*bundledMessage),
	}
}

// Topics returns an iterator which returns all of the topics for the client's project.
func (c *Client) Topics(ctx context.Context) *TopicIterator {
	return &TopicIterator{
		s:    c.s,
		next: c.s.listProjectTopics(ctx, c.fullyQualifiedProjectName()),
	}
}

// TopicIterator is an iterator that returns a series of topics.
type TopicIterator struct {
	s    service
	next nextStringFunc
}

// Next returns the next topic. If there are no more topics, iterator.Done will be returned.
func (tps *TopicIterator) Next() (*Topic, error) {
	topicName, err := tps.next()
	if err != nil {
		return nil, err
	}
	return newTopic(tps.s, topicName), nil
}

// ID returns the unique idenfier of the topic within its project.
func (t *Topic) ID() string {
	slash := strings.LastIndex(t.name, "/")
	if slash == -1 {
		// name is not a fully-qualified name.
		panic("bad topic name")
	}
	return t.name[slash+1:]
}

// String returns the printable globally unique name for the topic.
func (t *Topic) String() string {
	return t.name
}

// Delete deletes the topic.
func (t *Topic) Delete(ctx context.Context) error {
	return t.s.deleteTopic(ctx, t.name)
}

// Exists reports whether the topic exists on the server.
func (t *Topic) Exists(ctx context.Context) (bool, error) {
	if t.name == "_deleted-topic_" {
		return false, nil
	}

	return t.s.topicExists(ctx, t.name)
}

func (t *Topic) IAM() *iam.Handle {
	return t.s.iamHandle(t.name)
}

// Subscriptions returns an iterator which returns the subscriptions for this topic.
func (t *Topic) Subscriptions(ctx context.Context) *SubscriptionIterator {
	// NOTE: zero or more Subscriptions that are ultimately returned by this
	// Subscriptions iterator may belong to a different project to t.
	return &SubscriptionIterator{
		s:    t.s,
		next: t.s.listTopicSubscriptions(ctx, t.name),
	}
}

var errTopicStopped = errors.New("pubsub: Stop has been called for this topic")

// Publish publishes msg to the topic asynchronously. Messages are batched and
// sent according to the topic's PublishSettings. Publish never blocks.
//
// Publish returns a non-nil PublishResult which will be ready when the
// message has been sent (or has failed to be sent) to the server.
//
// Publish creates goroutines for batching and sending messages. These goroutines
// need to be stopped by calling t.Stop(). Once stopped, future calls to Publish
// will immediately return a PublishResult with an error.
func (t *Topic) Publish(ctx context.Context, msg *Message) *PublishResult {
	// TODO(jba): if this turns out to take significant time, try to approximate it.
	// Or, convert the messages to protos in Publish, instead of in the service.
	msg.size = proto.Size(&pb.PubsubMessage{
		Data:       msg.Data,
		Attributes: msg.Attributes,
	})
	r := &PublishResult{ready: make(chan struct{})}
	t.initBundler()
	t.mu.RLock()
	defer t.mu.RUnlock()
	// TODO(aboulhosn) [from bcmills] consider changing the semantics of bundler to perform this logic so we don't have to do it here
	if t.stopped {
		r.set("", errTopicStopped)
		return r
	}

	// TODO(jba) [from bcmills] consider using a shared channel per bundle
	// (requires Bundler API changes; would reduce allocations)
	// The call to Add should never return an error because the bundler's
	// BufferedByteLimit is set to MaxInt64; we do not perform any flow
	// control in the client.
	err := t.bundler.Add(&bundledMessage{msg, r}, msg.size)
	if err != nil {
		r.set("", err)
	}
	return r
}

// Send all remaining published messages and stop goroutines created for handling
// publishing. Returns once all outstanding messages have been sent or have
// failed to be sent.
func (t *Topic) Stop() {
	t.mu.Lock()
	noop := t.stopped || t.bundler == nil
	t.stopped = true
	t.mu.Unlock()
	if noop {
		return
	}
	t.bundler.Flush()
	// At this point, all pending bundles have been published and the bundler's
	// goroutines have exited, so it is OK for this goroutine to close bundlec.
	close(t.bundlec)
	t.wg.Wait()
}

// A PublishResult holds the result from a call to Publish.
type PublishResult struct {
	ready    chan struct{}
	serverID string
	err      error
}

// Ready returns a channel that is closed when the result is ready.
// When the Ready channel is closed, Get is guaranteed not to block.
func (r *PublishResult) Ready() <-chan struct{} { return r.ready }

// Get returns the server-generated message ID and/or error result of a Publish call.
// Get blocks until the Publish call completes or the context is done.
func (r *PublishResult) Get(ctx context.Context) (serverID string, err error) {
	// If the result is already ready, return it even if the context is done.
	select {
	case <-r.Ready():
		return r.serverID, r.err
	default:
	}
	select {
	case <-ctx.Done():
		return "", ctx.Err()
	case <-r.Ready():
		return r.serverID, r.err
	}
}

func (r *PublishResult) set(sid string, err error) {
	r.serverID = sid
	r.err = err
	close(r.ready)
}

type bundledMessage struct {
	msg *Message
	res *PublishResult
}

func (t *Topic) initBundler() {
	t.mu.RLock()
	noop := t.stopped || t.bundler != nil
	t.mu.RUnlock()
	if noop {
		return
	}
	t.mu.Lock()
	defer t.mu.Unlock()
	// Must re-check, since we released the lock.
	if t.stopped || t.bundler != nil {
		return
	}

	// TODO(jba): use a context detached from the one passed to NewClient.
	ctx := context.TODO()
	// Unless overridden, run several goroutines per CPU to call the Publish RPC.
	n := t.PublishSettings.NumGoroutines
	if n <= 0 {
		n = 25 * runtime.GOMAXPROCS(0)
	}
	timeout := t.PublishSettings.Timeout
	t.wg.Add(n)
	for i := 0; i < n; i++ {
		go func() {
			defer t.wg.Done()
			for b := range t.bundlec {
				bctx := ctx
				cancel := func() {}
				if timeout != 0 {
					bctx, cancel = context.WithTimeout(ctx, timeout)
				}
				t.publishMessageBundle(bctx, b)
				cancel()
			}
		}()
	}
	t.bundler = bundler.NewBundler(&bundledMessage{}, func(items interface{}) {
		t.bundlec <- items.([]*bundledMessage)

	})
	t.bundler.DelayThreshold = t.PublishSettings.DelayThreshold
	t.bundler.BundleCountThreshold = t.PublishSettings.CountThreshold
	if t.bundler.BundleCountThreshold > MaxPublishRequestCount {
		t.bundler.BundleCountThreshold = MaxPublishRequestCount
	}
	t.bundler.BundleByteThreshold = t.PublishSettings.ByteThreshold
	t.bundler.BufferedByteLimit = math.MaxInt64
	t.bundler.BundleByteLimit = MaxPublishRequestBytes
}

func (t *Topic) publishMessageBundle(ctx context.Context, bms []*bundledMessage) {
	msgs := make([]*Message, len(bms))
	for i, bm := range bms {
		msgs[i], bm.msg = bm.msg, nil // release bm.msg for GC
	}
	ids, err := t.s.publishMessages(ctx, t.name, msgs)
	for i, bm := range bms {
		if err != nil {
			bm.res.set("", err)
		} else {
			bm.res.set(ids[i], nil)
		}
	}
}
<commit_msg>pubsub: use correctly sized value for BufferedByteLimit<commit_after>// Copyright 2016 Google Inc. All Rights Reserved.
//
// 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 pubsub

import (
	"errors"
	"fmt"
	"runtime"
	"strings"
	"sync"
	"time"

	"cloud.google.com/go/iam"
	"github.com/golang/protobuf/proto"
	"golang.org/x/net/context"
	"google.golang.org/api/support/bundler"
	pb "google.golang.org/genproto/googleapis/pubsub/v1"
)

const (
	// The maximum number of messages that can be in a single publish request, as
	// determined by the PubSub service.
	MaxPublishRequestCount = 1000

	// The maximum size of a single publish request in bytes, as determined by the PubSub service.
	MaxPublishRequestBytes = 1e7

	maxInt = int(^uint(0) >> 1)
)

// ErrOversizedMessage indicates that a message's size exceeds MaxPublishRequestBytes.
var ErrOversizedMessage = bundler.ErrOversizedItem

// Topic is a reference to a PubSub topic.
type Topic struct {
	s service
	// The fully qualified identifier for the topic, in the format "projects/<projid>/topics/<name>"
	name string

	// Settings for publishing messages. All changes must be made before the
	// first call to Publish. The default is DefaultPublishSettings.
	PublishSettings PublishSettings

	mu      sync.RWMutex
	stopped bool
	bundler *bundler.Bundler

	wg sync.WaitGroup

	// Channel for message bundles to be published. Close to indicate that Stop was called.
	bundlec chan []*bundledMessage
}

// PublishSettings control the bundling of published messages.
type PublishSettings struct {

	// Publish a non-empty batch after this delay has passed.
	DelayThreshold time.Duration

	// Publish a batch when it has this many messages. The maximum is
	// MaxPublishRequestCount.
	CountThreshold int

	// Publish a batch when its size in bytes reaches this value.
	ByteThreshold int

	// The number of goroutines that invoke the Publish RPC concurrently.
	// Defaults to a multiple of GOMAXPROCS.
	NumGoroutines int

	// The maximum time that the client will attempt to publish a bundle of messages.
	Timeout time.Duration
}

// DefaultPublishSettings holds the default values for topics' PublishSettings.
var DefaultPublishSettings = PublishSettings{
	DelayThreshold: 1 * time.Millisecond,
	CountThreshold: 100,
	ByteThreshold:  1e6,
	Timeout:        60 * time.Second,
}

// CreateTopic creates a new topic.
// The specified topic ID must start with a letter, and contain only letters
// ([A-Za-z]), numbers ([0-9]), dashes (-), underscores (_), periods (.),
// tildes (~), plus (+) or percent signs (%). It must be between 3 and 255
// characters in length, and must not start with "goog".
// If the topic already exists an error will be returned.
func (c *Client) CreateTopic(ctx context.Context, id string) (*Topic, error) {
	t := c.Topic(id)
	err := c.s.createTopic(ctx, t.name)
	return t, err
}

// Topic creates a reference to a topic.
//
// If a Topic's Publish method is called, it has background goroutines
// associated with it. Clean them up by calling Topic.Stop.
//
// Avoid creating many Topic instances if you use them to publish.
func (c *Client) Topic(id string) *Topic {
	return newTopic(c.s, fmt.Sprintf("projects/%s/topics/%s", c.projectID, id))
}

func newTopic(s service, name string) *Topic {
	// bundlec is unbuffered. A buffer would occupy memory not
	// accounted for by the bundler, so BufferedByteLimit would be a lie:
	// the actual memory consumed would be higher.
	return &Topic{
		s:               s,
		name:            name,
		PublishSettings: DefaultPublishSettings,
		bundlec:         make(chan []*bundledMessage),
	}
}

// Topics returns an iterator which returns all of the topics for the client's project.
func (c *Client) Topics(ctx context.Context) *TopicIterator {
	return &TopicIterator{
		s:    c.s,
		next: c.s.listProjectTopics(ctx, c.fullyQualifiedProjectName()),
	}
}

// TopicIterator is an iterator that returns a series of topics.
type TopicIterator struct {
	s    service
	next nextStringFunc
}

// Next returns the next topic. If there are no more topics, iterator.Done will be returned.
func (tps *TopicIterator) Next() (*Topic, error) {
	topicName, err := tps.next()
	if err != nil {
		return nil, err
	}
	return newTopic(tps.s, topicName), nil
}

// ID returns the unique idenfier of the topic within its project.
func (t *Topic) ID() string {
	slash := strings.LastIndex(t.name, "/")
	if slash == -1 {
		// name is not a fully-qualified name.
		panic("bad topic name")
	}
	return t.name[slash+1:]
}

// String returns the printable globally unique name for the topic.
func (t *Topic) String() string {
	return t.name
}

// Delete deletes the topic.
func (t *Topic) Delete(ctx context.Context) error {
	return t.s.deleteTopic(ctx, t.name)
}

// Exists reports whether the topic exists on the server.
func (t *Topic) Exists(ctx context.Context) (bool, error) {
	if t.name == "_deleted-topic_" {
		return false, nil
	}

	return t.s.topicExists(ctx, t.name)
}

func (t *Topic) IAM() *iam.Handle {
	return t.s.iamHandle(t.name)
}

// Subscriptions returns an iterator which returns the subscriptions for this topic.
func (t *Topic) Subscriptions(ctx context.Context) *SubscriptionIterator {
	// NOTE: zero or more Subscriptions that are ultimately returned by this
	// Subscriptions iterator may belong to a different project to t.
	return &SubscriptionIterator{
		s:    t.s,
		next: t.s.listTopicSubscriptions(ctx, t.name),
	}
}

var errTopicStopped = errors.New("pubsub: Stop has been called for this topic")

// Publish publishes msg to the topic asynchronously. Messages are batched and
// sent according to the topic's PublishSettings. Publish never blocks.
//
// Publish returns a non-nil PublishResult which will be ready when the
// message has been sent (or has failed to be sent) to the server.
//
// Publish creates goroutines for batching and sending messages. These goroutines
// need to be stopped by calling t.Stop(). Once stopped, future calls to Publish
// will immediately return a PublishResult with an error.
func (t *Topic) Publish(ctx context.Context, msg *Message) *PublishResult {
	// TODO(jba): if this turns out to take significant time, try to approximate it.
	// Or, convert the messages to protos in Publish, instead of in the service.
	msg.size = proto.Size(&pb.PubsubMessage{
		Data:       msg.Data,
		Attributes: msg.Attributes,
	})
	r := &PublishResult{ready: make(chan struct{})}
	t.initBundler()
	t.mu.RLock()
	defer t.mu.RUnlock()
	// TODO(aboulhosn) [from bcmills] consider changing the semantics of bundler to perform this logic so we don't have to do it here
	if t.stopped {
		r.set("", errTopicStopped)
		return r
	}

	// TODO(jba) [from bcmills] consider using a shared channel per bundle
	// (requires Bundler API changes; would reduce allocations)
	// The call to Add should never return an error because the bundler's
	// BufferedByteLimit is set to maxInt; we do not perform any flow
	// control in the client.
	err := t.bundler.Add(&bundledMessage{msg, r}, msg.size)
	if err != nil {
		r.set("", err)
	}
	return r
}

// Send all remaining published messages and stop goroutines created for handling
// publishing. Returns once all outstanding messages have been sent or have
// failed to be sent.
func (t *Topic) Stop() {
	t.mu.Lock()
	noop := t.stopped || t.bundler == nil
	t.stopped = true
	t.mu.Unlock()
	if noop {
		return
	}
	t.bundler.Flush()
	// At this point, all pending bundles have been published and the bundler's
	// goroutines have exited, so it is OK for this goroutine to close bundlec.
	close(t.bundlec)
	t.wg.Wait()
}

// A PublishResult holds the result from a call to Publish.
type PublishResult struct {
	ready    chan struct{}
	serverID string
	err      error
}

// Ready returns a channel that is closed when the result is ready.
// When the Ready channel is closed, Get is guaranteed not to block.
func (r *PublishResult) Ready() <-chan struct{} { return r.ready }

// Get returns the server-generated message ID and/or error result of a Publish call.
// Get blocks until the Publish call completes or the context is done.
func (r *PublishResult) Get(ctx context.Context) (serverID string, err error) {
	// If the result is already ready, return it even if the context is done.
	select {
	case <-r.Ready():
		return r.serverID, r.err
	default:
	}
	select {
	case <-ctx.Done():
		return "", ctx.Err()
	case <-r.Ready():
		return r.serverID, r.err
	}
}

func (r *PublishResult) set(sid string, err error) {
	r.serverID = sid
	r.err = err
	close(r.ready)
}

type bundledMessage struct {
	msg *Message
	res *PublishResult
}

func (t *Topic) initBundler() {
	t.mu.RLock()
	noop := t.stopped || t.bundler != nil
	t.mu.RUnlock()
	if noop {
		return
	}
	t.mu.Lock()
	defer t.mu.Unlock()
	// Must re-check, since we released the lock.
	if t.stopped || t.bundler != nil {
		return
	}

	// TODO(jba): use a context detached from the one passed to NewClient.
	ctx := context.TODO()
	// Unless overridden, run several goroutines per CPU to call the Publish RPC.
	n := t.PublishSettings.NumGoroutines
	if n <= 0 {
		n = 25 * runtime.GOMAXPROCS(0)
	}
	timeout := t.PublishSettings.Timeout
	t.wg.Add(n)
	for i := 0; i < n; i++ {
		go func() {
			defer t.wg.Done()
			for b := range t.bundlec {
				bctx := ctx
				cancel := func() {}
				if timeout != 0 {
					bctx, cancel = context.WithTimeout(ctx, timeout)
				}
				t.publishMessageBundle(bctx, b)
				cancel()
			}
		}()
	}
	t.bundler = bundler.NewBundler(&bundledMessage{}, func(items interface{}) {
		t.bundlec <- items.([]*bundledMessage)

	})
	t.bundler.DelayThreshold = t.PublishSettings.DelayThreshold
	t.bundler.BundleCountThreshold = t.PublishSettings.CountThreshold
	if t.bundler.BundleCountThreshold > MaxPublishRequestCount {
		t.bundler.BundleCountThreshold = MaxPublishRequestCount
	}
	t.bundler.BundleByteThreshold = t.PublishSettings.ByteThreshold
	t.bundler.BufferedByteLimit = maxInt
	t.bundler.BundleByteLimit = MaxPublishRequestBytes
}

func (t *Topic) publishMessageBundle(ctx context.Context, bms []*bundledMessage) {
	msgs := make([]*Message, len(bms))
	for i, bm := range bms {
		msgs[i], bm.msg = bm.msg, nil // release bm.msg for GC
	}
	ids, err := t.s.publishMessages(ctx, t.name, msgs)
	for i, bm := range bms {
		if err != nil {
			bm.res.set("", err)
		} else {
			bm.res.set(ids[i], nil)
		}
	}
}
<|endoftext|>


<commit_before>package base

import(
	"encoding/json"
	"crypto/md5"
	"crypto/sha256"
	"encoding/hex"
	"encoding/base64"
	"strconv"
	"time"
	"strings"
)

func CreateToken(iss string, name string) string{

	/* 	Función que recopila la información del usuario y lo codifica para formar el token del tipo JWT.
		Recibe el ACCOUNT_ID y el nombre completo del usuario para codificarlos dentro del token */

	header := []byte(`{"alg":"HS256","typ":"JWT"}`)

	exp := int(time.Now().Unix()) + 86400    // Tiempo de expiración del token más 1 día
	payload := []byte(`{"iss":"`+iss+`","exp":`+strconv.Itoa(exp)+`,"name":"`+name+`"}`)

	signature := base64.StdEncoding.EncodeToString(header)+"."+base64.StdEncoding.EncodeToString(payload)

	// Forma un token del tipo [header].[payload].[signature]
	token := base64.StdEncoding.EncodeToString(header)+"."+base64.StdEncoding.EncodeToString(payload)+"."+EncryptToStringSha256(signature)
	return token
}

func CheckToken(token string) bool {

	/* Función que verifica la expliración del token y retorna true si es válido o false en caso contrario */

	tokSplit := strings.Split(token, ".")
	payloadJson, _ := base64.StdEncoding.DecodeString(tokSplit[1]) // Divide el token y extrae la parte de payload

	type Payload struct {
		Iss  	string   	`json:"iss"`
		Name 	string   	`json:"name"`// Se almacenarán los datos del payload
		Exp 	int 		`json:"exp"`
	}

	pl := Payload{}
	json.Unmarshal(payloadJson, &pl)
	// exp, _ := strconv.Atoi(pl.Exp) // Convierte string a int

	if pl.Exp < int(time.Now().Unix()) {
		return false
	} else {
		return true
	}

}

// Funciónes que reciben un texto, lo codifica y lo devuelve como un string
func EncryptToString(text string) string{
	hasher := md5.New()
	hasher.Write([]byte(text))
	return hex.EncodeToString(hasher.Sum(nil))
}
func EncryptToStringSha256(text string) string{
	hasher := sha256.New()
	hasher.Write([]byte(text))
	return hex.EncodeToString(hasher.Sum(nil))
}<commit_msg>Verifica que exista el id en la base de datos y que sea un token valido<commit_after>package base

import(
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
	"encoding/json"
	"crypto/md5"
	"crypto/sha256"
	"encoding/hex"
	"encoding/base64"
	"strconv"
	"time"
	"strings"
	"mr/app/models"
	"fmt"
)

func CreateToken(iss string, name string) string{

	/* 	Función que recopila la información del usuario y lo codifica para formar el token del tipo JWT.
		Recibe el ACCOUNT_ID y el nombre completo del usuario para codificarlos dentro del token */

	header := []byte(`{"alg":"HS256","typ":"JWT"}`)

	exp := int(time.Now().Unix()) + 86400    // Tiempo de expiración del token más 1 día
	payload := []byte(`{"iss":"`+iss+`","exp":`+strconv.Itoa(exp)+`,"name":"`+name+`"}`)

	signature := base64.StdEncoding.EncodeToString(header)+"."+base64.StdEncoding.EncodeToString(payload)

	// Forma un token del tipo [header].[payload].[signature]
	token := base64.StdEncoding.EncodeToString(header)+"."+base64.StdEncoding.EncodeToString(payload)+"."+EncryptToStringSha256(signature)
	return token
}

func CheckToken(token string) bool {

	/* Función que verifica la expliración del token y retorna true si es válido o false en caso contrario */

	tokSplit := strings.Split(token, ".")
	fmt.Println(len(tokSplit))
	if (len(tokSplit) != 3) {
		return false
	}

	payloadJson, err := base64.StdEncoding.DecodeString(tokSplit[1]) // Divide el token y extrae la parte de payload

	if err != nil{
		return false
	}

	type Payload struct {
		Iss  	string   	`json:"iss"`
		Name 	string   	`json:"name"`// Se almacenarán los datos del payload
		Exp 	int 		`json:"exp"`
	}

	pl := Payload{}
	json.Unmarshal(payloadJson, &pl)

	session, err := mgo.Dial(HostDB)

	if err != nil {
		return false
    }
    defer session.Close()

    session.SetMode(mgo.Monotonic, true)
    con := session.DB(NameDB).C(CollectionDB)

    result := models.Usuario{}
    err = con.Find(bson.M{"_id":bson.ObjectIdHex(pl.Iss)}).One(&result)

    if err != nil {  //Si no se encuentra el usuario en la base de datos retorna falso
    	return false
    } else if pl.Exp < int(time.Now().Unix()) {
		return false
	} else {
		return true
	}

}

// Funciónes que reciben un texto, lo codifica y lo devuelve como un string
func EncryptToString(text string) string{
	hasher := md5.New()
	hasher.Write([]byte(text))
	return hex.EncodeToString(hasher.Sum(nil))
}
func EncryptToStringSha256(text string) string{
	hasher := sha256.New()
	hasher.Write([]byte(text))
	return hex.EncodeToString(hasher.Sum(nil))
}<|endoftext|>