<commit_before>package mpdb

import (
	"database/sql"
	"github.com/kierdavis/mealplanner/mpdata"
)

const SearchTextFunc = "CONCAT(meal.name, ' ', meal.recipe, ' ', IFNULL((SELECT GROUP_CONCAT(tag.tag SEPARATOR ' ') FROM tag WHERE tag.mealid = meal.id), ''))"

// SQL statement for listing meals.
const ListMealsSQL = "SELECT meal.id, meal.name, meal.recipe, meal.favourite FROM meal"

// SQL statement for listing meals sorted by name.
const ListMealsByNameSQL = ListMealsSQL + " ORDER BY meal.name"

const CreateSearchPatternsTableSQL = "CREATE TEMPORARY TABLE search_patterns (pattern VARCHAR(255))"

const InsertSearchPatternSQL = "INSERT INTO search_patterns VALUES (?)"

const SearchMealsSQL = "SELECT meal.id, meal.name, meal.recipe, meal.favourite FROM meal INNER JOIN search_patterns ON ((meal.name LIKE search_patterns.pattern) OR (meal.recipe LIKE search_patterns.pattern))"

const SearchMealsByNameSQL = SearchMealsSQL + " ORDER BY meal.name"

const DropSearchPatternsTableSQL = "DROP TABLE search_patterns"

// SQL statement for fetching information about a meal.
const GetMealSQL = "SELECT meal.name, meal.recipe, meal.favourite FROM meal WHERE meal.id = ?"

// SQL statement for fetching tags associated with a meal.
const GetMealTagsSQL = "SELECT tag.tag FROM tag WHERE tag.mealid = ?"

const UpdateSearchTextSQL = "UPDATE meal SET meal.searchtext = " + SearchTextFunc + " WHERE meal.id = ?"

// SQL statement for adding a meal.
const AddMealSQL = "INSERT INTO meal VALUES (NULL, ?, ?, ?, '')"

// SQL statement for updating the information about a meal.
const UpdateMealSQL = "UPDATE meal SET meal.name = ?, meal.recipe = ?, meal.favourite = ? WHERE meal.id = ?"

// SQL statement for deleting all tags associated with a meal.
const DeleteMealTagsSQL = "DELETE FROM tag WHERE tag.mealid = ?"

// SQL statement for adding a tag to a meal.
const AddMealTagSQL = "INSERT INTO tag VALUES (?, ?)"

// SQL statement for testing whether a meal is marked as a favourite.
const IsFavouriteSQL = "SELECT meal.favourite FROM meal WHERE meal.id = ?"

// SQL statement to set the "favourite" status of a meal.
const SetFavouriteSQL = "UPDATE meal SET meal.favourite = ? WHERE meal.id = ?"

// SQL statement to delete a meal.
const DeleteMealSQL = "DELETE FROM meal WHERE meal.id = ?"

// SQL statement to list all tags in the database.
const ListAllTagsSQL = "SELECT DISTINCT tag.tag FROM tag"

// SQL statement to list all tags in the database sorted by name.
const ListAllTagsByNameSQL = "SELECT DISTINCT tag.tag FROM tag ORDER BY tag.tag ASC"

// ListMeals fetches and returns a list of all meals in the database. If the
// parameter 'sortByName' is true, the meals are sorted in alphabetical order
// by name.
func ListMeals(q Queryable, sortByName bool) (meals []*mpdata.Meal, err error) {
	var query string
	if sortByName {
		query = ListMealsByNameSQL
	} else {
		query = ListMealsSQL
	}

	return readMeals(q, query)
}

// ListMealsWithTags fetches and returns a list of all meals in the database
// with their associated tags. If the parameter 'sortByName' is true, the meals
// are sorted in alphabetical order by name.
func ListMealsWithTags(q Queryable, sortByName bool) (mts []mpdata.MealWithTags, err error) {
	meals, err := ListMeals(q, sortByName)
	if err != nil {
		return nil, err
	}

	return AttachMealTags(q, meals)
}

func insertSearchWords(q Queryable, words []string) (err error) {
	stmt, err := q.Prepare(InsertSearchPatternSQL)
	if err != nil {
		return err
	}
	defer stmt.Close()

	for _, word := range words {
		_, err = stmt.Exec("%" + word + "%")
		if err != nil {
			return err
		}
	}

	return nil
}

func SearchMeals(q Queryable, words []string, sortByName bool) (meals []*mpdata.Meal, err error) {
	_, err = q.Exec(CreateSearchPatternsTableSQL)
	if err != nil {
		return nil, err
	}

	err = insertSearchWords(q, words)
	if err != nil {
		return nil, err
	}

	var query string
	if sortByName {
		query = SearchMealsByNameSQL
	} else {
		query = SearchMealsSQL
	}

	meals, err = readMeals(q, query)
	if err != nil {
		return nil, err
	}

	_, err = q.Exec(DropSearchPatternsTableSQL)
	if err != nil {
		return nil, err
	}

	return meals, nil
}

func SearchMealsWithTags(q Queryable, words []string, sortByName bool) (mts []mpdata.MealWithTags, err error) {
	meals, err := SearchMeals(q, words, sortByName)
	if err != nil {
		return nil, err
	}

	return AttachMealTags(q, meals)
}

func readMeals(q Queryable, query string) (meals []*mpdata.Meal, err error) {
	rows, err := q.Query(query)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	for rows.Next() {
		meal := &mpdata.Meal{}

		err = rows.Scan(&meal.ID, &meal.Name, &meal.RecipeURL, &meal.Favourite)
		if err != nil {
			return nil, err
		}

		meals = append(meals, meal)
	}

	err = rows.Err()
	if err != nil {
		return nil, err
	}

	return meals, nil
}

func AttachMealTags(q Queryable, meals []*mpdata.Meal) (mts []mpdata.MealWithTags, err error) {
	getTagsStmt, err := q.Prepare(GetMealTagsSQL)
	if err != nil {
		return nil, err
	}
	defer getTagsStmt.Close()

	for _, meal := range meals {
		tags, err := getMealTagsPrepared(getTagsStmt, meal.ID)
		if err != nil {
			return nil, err
		}

		mt := mpdata.MealWithTags{
			Meal: meal,
			Tags: tags,
		}
		mts = append(mts, mt)
	}

	return mts, nil
}

// GetMeal fetches information from the database about the meal identified by
// 'mealID'.
func GetMeal(q Queryable, mealID uint64) (meal *mpdata.Meal, err error) {
	meal = &mpdata.Meal{ID: mealID}
	err = q.QueryRow(GetMealSQL, mealID).Scan(&meal.Name, &meal.RecipeURL, &meal.Favourite)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, nil
		}

		return nil, err
	}

	return meal, nil
}

// GetMealTags fetches the list of tags associated with the meal identified by
// 'mealID'.
func GetMealTags(q Queryable, mealID uint64) (tags []string, err error) {
	rows, err := q.Query(GetMealTagsSQL, mealID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	return readTags(rows)
}

// getMealTagsPrepared fetches the list of tags associated with the meal
// identified by 'mealID'. 'stmt' is assumed to be a prepared statement compiled
// from GetMealTagsSQL.
func getMealTagsPrepared(stmt *sql.Stmt, mealID uint64) (tags []string, err error) {
	rows, err := stmt.Query(mealID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	return readTags(rows)
}

// GetMealWithTags combines GetMeal and GetMealTags.
func GetMealWithTags(q Queryable, mealID uint64) (mt mpdata.MealWithTags, err error) {
	meal, err := GetMeal(q, mealID)
	if err != nil {
		return mpdata.MealWithTags{}, err
	}

	tags, err := GetMealTags(q, mealID)
	if err != nil {
		return mpdata.MealWithTags{}, err
	}

	return mpdata.MealWithTags{Meal: meal, Tags: tags}, nil
}

// AddMeal adds the information in 'meal' to the database as a new record, then
// sets 'meal.ID' to the identifier of this new record.
func AddMeal(q Queryable, meal *mpdata.Meal) (err error) {
	err = addMeal(q, meal)
	if err != nil {
		return err
	}
	return UpdateSearchText(q, meal.ID)
}

func addMeal(q Queryable, meal *mpdata.Meal) (err error) {
	result, err := q.Exec(AddMealSQL, meal.Name, meal.RecipeURL, meal.Favourite)
	if err != nil {
		return err
	}

	mealID, err := result.LastInsertId()
	if err != nil {
		return err
	}

	meal.ID = uint64(mealID)

	return nil
}

func UpdateSearchText(q Queryable, mealID uint64) (err error) {
	_, err = q.Exec(UpdateSearchTextSQL, mealID)
	return err
}

// AddMealTags adds the the list of tags given in 'tags' to the meal identified
// by 'mealID'.
func AddMealTags(q Queryable, mealID uint64, tags []string) (err error) {
	err = addMealTags(q, mealID, tags)
	if err != nil {
		return err
	}
	return UpdateSearchText(q, mealID)
}

func addMealTags(q Queryable, mealID uint64, tags []string) (err error) {
	stmt, err := q.Prepare(AddMealTagSQL)
	if err != nil {
		return err
	}
	defer stmt.Close()

	for _, tag := range tags {
		_, err = stmt.Exec(mealID, tag)
		if err != nil {
			return err
		}
	}

	return nil
}

// AddMealWithTags combines 'AddMeal' and 'AddMealTags'.
func AddMealWithTags(q Queryable, mt mpdata.MealWithTags) (err error) {
	err = addMealWithTags(q, mt)
	if err != nil {
		return err
	}
	return UpdateSearchText(q, mt.Meal.ID)
}

func addMealWithTags(q Queryable, mt mpdata.MealWithTags) (err error) {
	err = addMeal(q, mt.Meal)
	if err != nil {
		return err
	}

	return addMealTags(q, mt.Meal.ID, mt.Tags)
}

// UpdateMeal replaces with the information in the database for the meal
// identified by 'meal.ID' with the information in 'meal'.
func UpdateMeal(q Queryable, meal *mpdata.Meal) (err error) {
	err = updateMeal(q, meal)
	if err != nil {
		return err
	}
	return UpdateSearchText(q, meal.ID)
}

func updateMeal(q Queryable, meal *mpdata.Meal) (err error) {
	_, err = q.Exec(UpdateMealSQL, meal.Name, meal.RecipeURL, meal.Favourite, meal.ID)
	return err
}

// DeleteMealTags deletes all tags in the database associated with the meal
// identified by 'mealID'. If no such tags exist, no error is raised.
func DeleteMealTags(q Queryable, mealID uint64) (err error) {
	err = deleteMealTags(q, mealID)
	if err != nil {
		return err
	}
	return UpdateSearchText(q, mealID)
}

func deleteMealTags(q Queryable, mealID uint64) (err error) {
	_, err = q.Exec(DeleteMealTagsSQL, mealID)
	return err
}

// UpdateMealTags replaces the tags associated with the meal identified by
// 'mealID' with the list given by 'tags'.
func UpdateMealTags(q Queryable, mealID uint64, tags []string) (err error) {
	err = updateMealTags(q, mealID, tags)
	if err != nil {
		return err
	}
	return UpdateSearchText(q, mealID)
}

func updateMealTags(q Queryable, mealID uint64, tags []string) (err error) {
	err = deleteMealTags(q, mealID)
	if err != nil {
		return err
	}

	return addMealTags(q, mealID, tags)
}

// UpdateMealWithTags combines UpdateMeal and UpdateMealTags.
func UpdateMealWithTags(q Queryable, mt mpdata.MealWithTags) (err error) {
	err = updateMealWithTags(q, mt)
	if err != nil {
		return err
	}
	return UpdateSearchText(q, mt.Meal.ID)
}

func updateMealWithTags(q Queryable, mt mpdata.MealWithTags) (err error) {
	err = updateMeal(q, mt.Meal)
	if err != nil {
		return err
	}

	return updateMealTags(q, mt.Meal.ID, mt.Tags)
}

// ToggleFavourite toggles the "favourite" status of the meal identified by
// 'mealID', and returns the new favourite status.
func ToggleFavourite(q Queryable, mealID uint64) (isFavourite bool, err error) {
	err = q.QueryRow(IsFavouriteSQL, mealID).Scan(&isFavourite)
	if err != nil {
		return false, err
	}

	isFavourite = !isFavourite
	_, err = q.Exec(SetFavouriteSQL, isFavourite, mealID)
	return isFavourite, err
}

// DeleteMeal deletes the meal record identified by 'mealID'. If no such meal
// exists, no error is raised.
func DeleteMeal(q Queryable, mealID uint64) (err error) {
	_, err = q.Exec(DeleteMealSQL, mealID)
	return err
}

// DeleteMealWithTags deletes the meal record identified by 'mealID', and all
// tag records associated with it.
func DeleteMealWithTags(q Queryable, mealID uint64) (err error) {
	err = DeleteMeal(q, mealID)
	if err != nil {
		return err
	}

	return deleteMealTags(q, mealID)
}

// ListAllTags returns a list (without duplicates) of all tags that appear in
// the database. If the 'sortByName' parameter is true, the tags are sorted into
// alphabetical order.
func ListAllTags(q Queryable, sortByName bool) (tags []string, err error) {
	var query string
	if sortByName {
		query = ListAllTagsByNameSQL
	} else {
		query = ListAllTagsSQL
	}

	rows, err := q.Query(query)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	return readTags(rows)
}

// readTags reads a *sql.Rows as produced by GetMealTags or
// getMealTagsPrepared and converts it into a slice of tags.
func readTags(rows *sql.Rows) (tags []string, err error) {
	var tag string

	for rows.Next() {
		err = rows.Scan(&tag)
		if err != nil {
			return nil, err
		}

		tags = append(tags, tag)
	}

	err = rows.Err()
	if err != nil {
		return nil, err
	}

	return tags, nil
}
<commit_msg>Change search to be logical AND of words instead of OR<commit_after>package mpdb

import (
	"database/sql"
	"github.com/kierdavis/mealplanner/mpdata"
)

const SearchTextFunc = "CONCAT(meal.name, ' ', meal.recipe, ' ', IFNULL((SELECT GROUP_CONCAT(tag.tag SEPARATOR ' ') FROM tag WHERE tag.mealid = meal.id), ''))"

// SQL statement for listing meals.
const ListMealsSQL = "SELECT meal.id, meal.name, meal.recipe, meal.favourite FROM meal"

// SQL statement for listing meals sorted by name.
const ListMealsByNameSQL = ListMealsSQL + " ORDER BY meal.name"

const CreateSearchPatternsTableSQL = "CREATE TEMPORARY TABLE search_patterns (pattern VARCHAR(255))"

const InsertSearchPatternSQL = "INSERT INTO search_patterns VALUES (?)"

const DropSearchPatternsTableSQL = "DROP TABLE search_patterns"

// SQL statement for fetching information about a meal.
const GetMealSQL = "SELECT meal.name, meal.recipe, meal.favourite FROM meal WHERE meal.id = ?"

// SQL statement for fetching tags associated with a meal.
const GetMealTagsSQL = "SELECT tag.tag FROM tag WHERE tag.mealid = ?"

const UpdateSearchTextSQL = "UPDATE meal SET meal.searchtext = " + SearchTextFunc + " WHERE meal.id = ?"

// SQL statement for adding a meal.
const AddMealSQL = "INSERT INTO meal VALUES (NULL, ?, ?, ?, '')"

// SQL statement for updating the information about a meal.
const UpdateMealSQL = "UPDATE meal SET meal.name = ?, meal.recipe = ?, meal.favourite = ? WHERE meal.id = ?"

// SQL statement for deleting all tags associated with a meal.
const DeleteMealTagsSQL = "DELETE FROM tag WHERE tag.mealid = ?"

// SQL statement for adding a tag to a meal.
const AddMealTagSQL = "INSERT INTO tag VALUES (?, ?)"

// SQL statement for testing whether a meal is marked as a favourite.
const IsFavouriteSQL = "SELECT meal.favourite FROM meal WHERE meal.id = ?"

// SQL statement to set the "favourite" status of a meal.
const SetFavouriteSQL = "UPDATE meal SET meal.favourite = ? WHERE meal.id = ?"

// SQL statement to delete a meal.
const DeleteMealSQL = "DELETE FROM meal WHERE meal.id = ?"

// SQL statement to list all tags in the database.
const ListAllTagsSQL = "SELECT DISTINCT tag.tag FROM tag"

// SQL statement to list all tags in the database sorted by name.
const ListAllTagsByNameSQL = "SELECT DISTINCT tag.tag FROM tag ORDER BY tag.tag ASC"

// ListMeals fetches and returns a list of all meals in the database. If the
// parameter 'sortByName' is true, the meals are sorted in alphabetical order
// by name.
func ListMeals(q Queryable, sortByName bool) (meals []*mpdata.Meal, err error) {
	var query string
	if sortByName {
		query = ListMealsByNameSQL
	} else {
		query = ListMealsSQL
	}

	rows, err := q.Query(query)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	return readMeals(rows)
}

// ListMealsWithTags fetches and returns a list of all meals in the database
// with their associated tags. If the parameter 'sortByName' is true, the meals
// are sorted in alphabetical order by name.
func ListMealsWithTags(q Queryable, sortByName bool) (mts []mpdata.MealWithTags, err error) {
	meals, err := ListMeals(q, sortByName)
	if err != nil {
		return nil, err
	}

	return AttachMealTags(q, meals)
}

func SearchMeals(q Queryable, words []string, sortByName bool) (meals []*mpdata.Meal, err error) {
	query := "SELECT meal.id, meal.name, meal.recipe, meal.favourite FROM meal"
	conjuctive := "WHERE"
	args := make([]interface{}, len(words))
	for i, word := range words {
		query += " " + conjuctive + " meal.searchtext LIKE ?"
		args[i] = "%" + word + "%"
		conjuctive = "AND"
	}

	if sortByName {
		query += " ORDER BY meal.name"
	}

	rows, err := q.Query(query, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	return readMeals(rows)
}

func SearchMealsWithTags(q Queryable, words []string, sortByName bool) (mts []mpdata.MealWithTags, err error) {
	meals, err := SearchMeals(q, words, sortByName)
	if err != nil {
		return nil, err
	}

	return AttachMealTags(q, meals)
}

func readMeals(rows *sql.Rows) (meals []*mpdata.Meal, err error) {
	for rows.Next() {
		meal := &mpdata.Meal{}

		err = rows.Scan(&meal.ID, &meal.Name, &meal.RecipeURL, &meal.Favourite)
		if err != nil {
			return nil, err
		}

		meals = append(meals, meal)
	}

	err = rows.Err()
	if err != nil {
		return nil, err
	}

	return meals, nil
}

func AttachMealTags(q Queryable, meals []*mpdata.Meal) (mts []mpdata.MealWithTags, err error) {
	getTagsStmt, err := q.Prepare(GetMealTagsSQL)
	if err != nil {
		return nil, err
	}
	defer getTagsStmt.Close()

	for _, meal := range meals {
		tags, err := getMealTagsPrepared(getTagsStmt, meal.ID)
		if err != nil {
			return nil, err
		}

		mt := mpdata.MealWithTags{
			Meal: meal,
			Tags: tags,
		}
		mts = append(mts, mt)
	}

	return mts, nil
}

// GetMeal fetches information from the database about the meal identified by
// 'mealID'.
func GetMeal(q Queryable, mealID uint64) (meal *mpdata.Meal, err error) {
	meal = &mpdata.Meal{ID: mealID}
	err = q.QueryRow(GetMealSQL, mealID).Scan(&meal.Name, &meal.RecipeURL, &meal.Favourite)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, nil
		}

		return nil, err
	}

	return meal, nil
}

// GetMealTags fetches the list of tags associated with the meal identified by
// 'mealID'.
func GetMealTags(q Queryable, mealID uint64) (tags []string, err error) {
	rows, err := q.Query(GetMealTagsSQL, mealID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	return readTags(rows)
}

// getMealTagsPrepared fetches the list of tags associated with the meal
// identified by 'mealID'. 'stmt' is assumed to be a prepared statement compiled
// from GetMealTagsSQL.
func getMealTagsPrepared(stmt *sql.Stmt, mealID uint64) (tags []string, err error) {
	rows, err := stmt.Query(mealID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	return readTags(rows)
}

// GetMealWithTags combines GetMeal and GetMealTags.
func GetMealWithTags(q Queryable, mealID uint64) (mt mpdata.MealWithTags, err error) {
	meal, err := GetMeal(q, mealID)
	if err != nil {
		return mpdata.MealWithTags{}, err
	}

	tags, err := GetMealTags(q, mealID)
	if err != nil {
		return mpdata.MealWithTags{}, err
	}

	return mpdata.MealWithTags{Meal: meal, Tags: tags}, nil
}

// AddMeal adds the information in 'meal' to the database as a new record, then
// sets 'meal.ID' to the identifier of this new record.
func AddMeal(q Queryable, meal *mpdata.Meal) (err error) {
	err = addMeal(q, meal)
	if err != nil {
		return err
	}
	return UpdateSearchText(q, meal.ID)
}

func addMeal(q Queryable, meal *mpdata.Meal) (err error) {
	result, err := q.Exec(AddMealSQL, meal.Name, meal.RecipeURL, meal.Favourite)
	if err != nil {
		return err
	}

	mealID, err := result.LastInsertId()
	if err != nil {
		return err
	}

	meal.ID = uint64(mealID)

	return nil
}

func UpdateSearchText(q Queryable, mealID uint64) (err error) {
	_, err = q.Exec(UpdateSearchTextSQL, mealID)
	return err
}

// AddMealTags adds the the list of tags given in 'tags' to the meal identified
// by 'mealID'.
func AddMealTags(q Queryable, mealID uint64, tags []string) (err error) {
	err = addMealTags(q, mealID, tags)
	if err != nil {
		return err
	}
	return UpdateSearchText(q, mealID)
}

func addMealTags(q Queryable, mealID uint64, tags []string) (err error) {
	stmt, err := q.Prepare(AddMealTagSQL)
	if err != nil {
		return err
	}
	defer stmt.Close()

	for _, tag := range tags {
		_, err = stmt.Exec(mealID, tag)
		if err != nil {
			return err
		}
	}

	return nil
}

// AddMealWithTags combines 'AddMeal' and 'AddMealTags'.
func AddMealWithTags(q Queryable, mt mpdata.MealWithTags) (err error) {
	err = addMealWithTags(q, mt)
	if err != nil {
		return err
	}
	return UpdateSearchText(q, mt.Meal.ID)
}

func addMealWithTags(q Queryable, mt mpdata.MealWithTags) (err error) {
	err = addMeal(q, mt.Meal)
	if err != nil {
		return err
	}

	return addMealTags(q, mt.Meal.ID, mt.Tags)
}

// UpdateMeal replaces with the information in the database for the meal
// identified by 'meal.ID' with the information in 'meal'.
func UpdateMeal(q Queryable, meal *mpdata.Meal) (err error) {
	err = updateMeal(q, meal)
	if err != nil {
		return err
	}
	return UpdateSearchText(q, meal.ID)
}

func updateMeal(q Queryable, meal *mpdata.Meal) (err error) {
	_, err = q.Exec(UpdateMealSQL, meal.Name, meal.RecipeURL, meal.Favourite, meal.ID)
	return err
}

// DeleteMealTags deletes all tags in the database associated with the meal
// identified by 'mealID'. If no such tags exist, no error is raised.
func DeleteMealTags(q Queryable, mealID uint64) (err error) {
	err = deleteMealTags(q, mealID)
	if err != nil {
		return err
	}
	return UpdateSearchText(q, mealID)
}

func deleteMealTags(q Queryable, mealID uint64) (err error) {
	_, err = q.Exec(DeleteMealTagsSQL, mealID)
	return err
}

// UpdateMealTags replaces the tags associated with the meal identified by
// 'mealID' with the list given by 'tags'.
func UpdateMealTags(q Queryable, mealID uint64, tags []string) (err error) {
	err = updateMealTags(q, mealID, tags)
	if err != nil {
		return err
	}
	return UpdateSearchText(q, mealID)
}

func updateMealTags(q Queryable, mealID uint64, tags []string) (err error) {
	err = deleteMealTags(q, mealID)
	if err != nil {
		return err
	}

	return addMealTags(q, mealID, tags)
}

// UpdateMealWithTags combines UpdateMeal and UpdateMealTags.
func UpdateMealWithTags(q Queryable, mt mpdata.MealWithTags) (err error) {
	err = updateMealWithTags(q, mt)
	if err != nil {
		return err
	}
	return UpdateSearchText(q, mt.Meal.ID)
}

func updateMealWithTags(q Queryable, mt mpdata.MealWithTags) (err error) {
	err = updateMeal(q, mt.Meal)
	if err != nil {
		return err
	}

	return updateMealTags(q, mt.Meal.ID, mt.Tags)
}

// ToggleFavourite toggles the "favourite" status of the meal identified by
// 'mealID', and returns the new favourite status.
func ToggleFavourite(q Queryable, mealID uint64) (isFavourite bool, err error) {
	err = q.QueryRow(IsFavouriteSQL, mealID).Scan(&isFavourite)
	if err != nil {
		return false, err
	}

	isFavourite = !isFavourite
	_, err = q.Exec(SetFavouriteSQL, isFavourite, mealID)
	return isFavourite, err
}

// DeleteMeal deletes the meal record identified by 'mealID'. If no such meal
// exists, no error is raised.
func DeleteMeal(q Queryable, mealID uint64) (err error) {
	_, err = q.Exec(DeleteMealSQL, mealID)
	return err
}

// DeleteMealWithTags deletes the meal record identified by 'mealID', and all
// tag records associated with it.
func DeleteMealWithTags(q Queryable, mealID uint64) (err error) {
	err = DeleteMeal(q, mealID)
	if err != nil {
		return err
	}

	return deleteMealTags(q, mealID)
}

// ListAllTags returns a list (without duplicates) of all tags that appear in
// the database. If the 'sortByName' parameter is true, the tags are sorted into
// alphabetical order.
func ListAllTags(q Queryable, sortByName bool) (tags []string, err error) {
	var query string
	if sortByName {
		query = ListAllTagsByNameSQL
	} else {
		query = ListAllTagsSQL
	}

	rows, err := q.Query(query)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	return readTags(rows)
}

// readTags reads a *sql.Rows as produced by GetMealTags or
// getMealTagsPrepared and converts it into a slice of tags.
func readTags(rows *sql.Rows) (tags []string, err error) {
	var tag string

	for rows.Next() {
		err = rows.Scan(&tag)
		if err != nil {
			return nil, err
		}

		tags = append(tags, tag)
	}

	err = rows.Err()
	if err != nil {
		return nil, err
	}

	return tags, nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"net/url"
	"os"
	"os/exec"
	"strings"

	"github.com/blang/mpv"
	"github.com/cnt0/twitch-streamsniper/api"
)

const ytdl = "youtube-dl"

var yt = flag.String("yt", "", "")

func main() {
	flag.Parse()
	c := mpv.NewClient(mpv.NewIPCClient("/tmp/mpvsocket"))
	if len(*yt) > 0 {
		c.SetProperty("ytdl-format", *yt)
	}

	if u, err := url.Parse(os.Args[len(os.Args)-1]); err == nil {
		if strings.HasSuffix(strings.ToLower(u.Hostname()), "twitch.tv") {
			var formats struct {
				Formats []api.FormatItem `json:"formats"`
			}
			data, err := exec.Command(ytdl, "-J", "--skip-download", os.Args[len(os.Args)-1]).Output()
			if err != nil {
				fmt.Println(err)
				return
			}
			if err := json.Unmarshal(data, &formats); err != nil {
				fmt.Println(err)
				return
			}

			for _, f := range formats.Formats {
				s := strings.ToLower(f.Format)
				if strings.Contains(s, "source") || strings.Contains(s, "1080p60") {
					c.Loadfile(f.URL, mpv.LoadFileModeReplace)
					return
				}
			}
			fmt.Println("Hmmm... What does hero truly need?")
			for i, f := range formats.Formats {
				fmt.Printf("%v: %v\n", i+1, f.Format)
			}
			var idx int
			fmt.Scan(&idx)
			c.Loadfile(formats.Formats[idx-1].URL, mpv.LoadFileModeReplace)
			return
		}
	}

	if len(os.Args) > 1 {
		c.Loadfile(os.Args[len(os.Args)-1], mpv.LoadFileModeReplace)
	}

}
<commit_msg>improve mpvd youtube & twitch handlers<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"net/url"
	"os"
	"os/exec"
	"strings"

	"github.com/blang/mpv"
	"github.com/cnt0/twitch-streamsniper/api"
)

const (
	ytdl      = "youtube-dl"
	mpvSocket = "/tmp/mpvsocket"
)

func main() {
	c := mpv.NewClient(mpv.NewIPCClient(mpvSocket))

	if u, err := url.Parse(os.Args[len(os.Args)-1]); err == nil {

		hostname := strings.ToLower(u.Hostname())
		var formats struct {
			Formats []api.FormatItem `json:"formats"`
		}

		if strings.HasSuffix(hostname, "twitch.tv") {
			data, err := exec.Command(ytdl, "-J", "--skip-download", os.Args[len(os.Args)-1]).Output()
			if err != nil {
				fmt.Println(err)
				return
			}
			if err := json.Unmarshal(data, &formats); err != nil {
				fmt.Printf("%v is likely offline\n", u.Path)
				return
			}

			for _, f := range formats.Formats {
				s := strings.ToLower(f.Format)
				if strings.Contains(s, "source") || strings.Contains(s, "1080p60") {
					c.Loadfile(f.URL, mpv.LoadFileModeReplace)
					return
				}
			}
			fmt.Println("Hmmm... What does hero truly need?")
			for i, f := range formats.Formats {
				fmt.Printf("%v: %v\n", i+1, f.Format)
			}
			var idx int
			fmt.Scan(&idx)
			c.Loadfile(formats.Formats[idx-1].URL, mpv.LoadFileModeReplace)
			return
		}

		if strings.HasSuffix(hostname, "youtube.com") || strings.HasSuffix(hostname, "youtu.be") {
			data, err := exec.Command(ytdl, "-J", "--skip-download", os.Args[len(os.Args)-1]).Output()
			if err != nil {
				fmt.Println(err)
				return
			}
			if err := json.Unmarshal(data, &formats); err != nil {
				fmt.Println(err)
				return
			}
			fmt.Println("Hmmm... What does hero truly need?")
			for i, f := range formats.Formats {
				fmt.Printf("%v: %v\n", i+1, f.Format)
			}
			desiredFormat := ""
			fmt.Scan(&desiredFormat)
			c.SetProperty("ytdl-format", desiredFormat)
			c.Loadfile(os.Args[len(os.Args)-1], mpv.LoadFileModeReplace)
			return
		}

	}

}
<|endoftext|>


<commit_before>package drivers

import (
	"github.com/klauspost/password"
	"github.com/klauspost/password/testdata"
	"github.com/klauspost/password/tokenizer"

	"bytes"
	"fmt"
)

type TestDB interface {
	password.DbWriter
	password.DB
}

// TestDriver will test a driver by
// running TestImport followed by TestData
func TestDriver(db TestDB) error {
	err := TestImport(db)
	if err != nil {
		return err
	}
	err = TestData(db)
	if err != nil {
		return err
	}
	return nil
}

var single_val = "j984lop!#\"{}"

// Runs the import part of the tests.
func TestImport(db password.DbWriter) error {
	buf, err := testdata.Asset("testdata.txt.gz")
	if err != nil {
		return err
	}
	in, err := tokenizer.NewGzLine(bytes.NewBuffer(buf))
	if err != nil {
		return err
	}
	writer, ok := db.(password.DbWriter)
	if !ok {
		return fmt.Errorf("%T is not a DbWriter", db)
	}
	err = password.Import(in, writer, nil)
	if err != nil {
		return err
	}
	// Test Add once separately
	err = writer.Add(single_val)
	if err != nil {
		return err
	}
	return nil
}

func TestData(db password.DB) error {
	for p := range testdata.TestSet {
		if password.SanitizeOK(p, nil) != nil {
			continue
		}
		err := password.Check(p, db, nil)
		if err != password.ErrPasswordInDB {
			return fmt.Errorf("%s not found in database: %v", p, err)
		}
		if err != password.ErrPasswordInDB && err != nil {
			return fmt.Errorf("check %s returned unexpected error: %v", p, err)
		}
	}
	for p := range testdata.NotInSet {
		if password.SanitizeOK(p, nil) != nil {
			continue
		}
		err := password.Check(p, db, nil)
		if err == password.ErrPasswordInDB {
			return fmt.Errorf("%s should NOT be not found in database: %v", p, err)
		} else if err != nil {
			return err
		}
	}

	has, err := db.Has(single_val)
	if !has {
		return fmt.Errorf("%s not found in database. (single insert)", single_val)
	}
	if err != nil {
		return err
	}
	has, err = db.Has(single_val + "*")
	if has {
		return fmt.Errorf("%s* WAS found in database, it shouldn't. (single insert)", single_val)
	}
	if err != nil {
		return err
	}
	return nil
}
<commit_msg>Test that duplicate entries are ok.<commit_after>package drivers

import (
	"github.com/klauspost/password"
	"github.com/klauspost/password/testdata"
	"github.com/klauspost/password/tokenizer"

	"bytes"
	"fmt"
)

type TestDB interface {
	password.DbWriter
	password.DB
}

// TestDriver will test a driver by
// running TestImport followed by TestData
func TestDriver(db TestDB) error {
	err := TestImport(db)
	if err != nil {
		return err
	}
	err = TestData(db)
	if err != nil {
		return err
	}
	return nil
}

var single_val = "j984lop!#\"{}"

// Runs the import part of the tests.
func TestImport(db password.DbWriter) error {
	buf, err := testdata.Asset("testdata.txt.gz")
	if err != nil {
		return err
	}
	in, err := tokenizer.NewGzLine(bytes.NewBuffer(buf))
	if err != nil {
		return err
	}
	writer, ok := db.(password.DbWriter)
	if !ok {
		return fmt.Errorf("%T is not a DbWriter", db)
	}
	err = password.Import(in, writer, nil)
	if err != nil {
		return err
	}
	// Test Add once separately
	err = writer.Add(single_val)
	if err != nil {
		return err
	}
	// .. and test that it is ok to write it again.
	err = writer.Add(single_val)
	if err != nil {
		return err
	}
	return nil
}

func TestData(db password.DB) error {
	for p := range testdata.TestSet {
		if password.SanitizeOK(p, nil) != nil {
			continue
		}
		err := password.Check(p, db, nil)
		if err != password.ErrPasswordInDB {
			return fmt.Errorf("%s not found in database: %v", p, err)
		}
		if err != password.ErrPasswordInDB && err != nil {
			return fmt.Errorf("check %s returned unexpected error: %v", p, err)
		}
	}
	for p := range testdata.NotInSet {
		if password.SanitizeOK(p, nil) != nil {
			continue
		}
		err := password.Check(p, db, nil)
		if err == password.ErrPasswordInDB {
			return fmt.Errorf("%s should NOT be not found in database: %v", p, err)
		} else if err != nil {
			return err
		}
	}

	has, err := db.Has(single_val)
	if !has {
		return fmt.Errorf("%s not found in database. (single insert)", single_val)
	}
	if err != nil {
		return err
	}
	has, err = db.Has(single_val + "*")
	if has {
		return fmt.Errorf("%s* WAS found in database, it shouldn't. (single insert)", single_val)
	}
	if err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/binary"
	"encoding/json"
	"log"
	"os"
	"path"

	"github.com/robbiev/multipass"
)

type LoginResponse struct {
	Success bool
	Items   []multipass.Item
}

func login(password []byte) LoginResponse {
	home := os.Getenv("HOME")
	onePasswordDir := path.Join(home, "/Dropbox/1password/1Password.agilekeychain/data/default")
	keyChain := multipass.NewAgileKeyChain(onePasswordDir)
	defer keyChain.Close()
	err := keyChain.Open(password)
	response := LoginResponse{Success: err == nil}
	if err == nil {
		var items []multipass.Item
		keyChain.ForEachItem(func(p string, f []byte) error {
			item, err := multipass.DecryptFile(f, keyChain.Keys())
			if err == nil {
				items = append(items, item)
			}
			return nil
		})
		response.Items = items
	}
	return response
}

func main() {
	defer func() {
		if r := recover(); r != nil {
			log.Println("Recovered in f", r)
		}
	}()

	f, err := os.OpenFile("/home/robbie/log.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		return
	}
	//defer f.Close()
	log.SetOutput(f)
	for {
		// get message length, 4 bytes
		var length uint32
		err := binary.Read(os.Stdin, binary.LittleEndian, &length)
		if err != nil {
			break
		}

		// read message
		message := make([]byte, length)
		_, e := os.Stdin.Read(message)
		if e != nil {
			break
		}

		log.Println("read message")
		log.Println(string(message))

		type Command struct {
			Action  string
			Payload interface{}
		}

		var command Command
		json.Unmarshal(message, &command)
		payload := command.Payload.(map[string]interface{})

		password := []byte(payload["Password"].(string))
		response := login(password)

		encoded, _ := json.Marshal(response)

		log.Println(string(encoded))
		log.Println(len(encoded))

		// simply echo the message back
		binary.Write(os.Stdout, binary.LittleEndian, uint32(len(encoded)))

		_, er := os.Stdout.Write(encoded)
		if er != nil {
			log.Println(er)
			break
		}
	}
}
<commit_msg>optional logging<commit_after>package main

import (
	"encoding/binary"
	"encoding/json"
	"io/ioutil"
	"log"
	"os"
	"path"

	"github.com/robbiev/multipass"
)

type LoginResponse struct {
	Success bool
	Items   []multipass.Item
}

func login(password []byte) LoginResponse {
	home := os.Getenv("HOME")
	onePasswordDir := path.Join(home, "/Dropbox/1password/1Password.agilekeychain/data/default")
	keyChain := multipass.NewAgileKeyChain(onePasswordDir)
	defer keyChain.Close()
	err := keyChain.Open(password)
	response := LoginResponse{Success: err == nil}
	if err == nil {
		var items []multipass.Item
		keyChain.ForEachItem(func(p string, f []byte) error {
			item, err := multipass.DecryptFile(f, keyChain.Keys())
			if err == nil {
				items = append(items, item)
			}
			return nil
		})
		response.Items = items
	}
	return response
}

func main() {
	if len(os.Getenv("MULTIPASS_LOGGING")) > 0 {
		f, err := os.OpenFile("/home/robbie/log.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			return
		}
		log.SetOutput(f)
	} else {
		log.SetOutput(ioutil.Discard)
	}

	defer func() {
		if r := recover(); r != nil {
			log.Println("Recovered in f", r)
		}
	}()

	for {
		// get message length, 4 bytes
		var length uint32
		err := binary.Read(os.Stdin, binary.LittleEndian, &length)
		if err != nil {
			break
		}

		// read message
		message := make([]byte, length)
		_, e := os.Stdin.Read(message)
		if e != nil {
			break
		}

		log.Println("read message")
		log.Println(string(message))

		type Command struct {
			Action  string
			Payload interface{}
		}

		var command Command
		json.Unmarshal(message, &command)
		payload := command.Payload.(map[string]interface{})

		password := []byte(payload["Password"].(string))
		response := login(password)

		encoded, _ := json.Marshal(response)

		log.Println(string(encoded))
		log.Println(len(encoded))

		// simply echo the message back
		binary.Write(os.Stdout, binary.LittleEndian, uint32(len(encoded)))

		_, er := os.Stdout.Write(encoded)
		if er != nil {
			log.Println(er)
			break
		}
	}
}
<|endoftext|>


<commit_before>// Copyright(c) 2015 Derek Collison (derek.collison@gmail.com)

package main

import (
	"bufio"
	"crypto/aes"
	"crypto/cipher"
	"crypto/sha256"
	"flag"
	"fmt"
	"log"
	"os"
	"os/signal"
	"runtime"

	"github.com/nats-io/nats"
)

func usage() {
	log.Fatalf("Usage: nats-chat <subject> <key>\n")
}

const (
	natsUrl    = "nats://demo.nats.io:4443"
	serverName = "demo.nats.io"
)

// Hold name, key and subject
var name, subj, key string
var keyHash []byte

// Cipher
var gcm cipher.AEAD
var nonce []byte

// Messages we send
type chat struct {
	Name string
	Msg  []byte
}

func main() {
	log.SetFlags(0)
	flag.Usage = usage
	flag.Parse()

	args := flag.Args()
	if len(args) < 1 {
		usage()
	}
	subj, key = args[0], args[1]
	h := sha256.New()
	h.Write([]byte(key))
	keyHash = h.Sum(nil)

	// Create cipher
	var err error
	block, err := aes.NewCipher(keyHash)
	if err != nil {
		log.Fatalf("Can't create cipher: %v\n", err)
	}
	gcm, err = cipher.NewGCMWithNonceSize(block, sha256.Size)
	if err != nil {
		log.Fatalf("Can't create gcm: %v\n", err)
	}

	// Generate the nonce
	h.Write([]byte(subj))
	h.Write(keyHash)
	nonce = h.Sum(nil)

	// Connect securely to NATS
	nc, err := nats.SecureConnect(natsUrl)
	if err != nil {
		log.Fatalf("Got an error on Connect with Secure Options: %+v\n", err)
	}
	log.Printf("Securely connected to %s", natsUrl)
	ec, _ := nats.NewEncodedConn(nc, nats.GOB_ENCODER)

	// Setup signal handlers to signal leaving.
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill)
	go func() {
		for range c {
			fmt.Printf("\n\n")
			if name != "" {
				exit := &chat{Name: name, Msg: encrypt("<left>\n")}
				ec.Publish(subj, exit)
				ec.Flush()
			}
			os.Exit(0)
		}
	}()

	// Collect the name
	fmt.Printf("Enter Name: ")
	fmt.Scanln(&name)

	// Create a reader for stdin
	reader := bufio.NewReader(os.Stdin)

	// Subscribe to messages
	ec.Subscribe(subj, func(msg *chat) {
		if msg.Name == name {
			return
		}
		fmt.Printf("\033[2K\r[%s] %s", msg.Name, decrypt(msg))
		fmt.Printf("[%s] ", name)
	})

	// Send welcome
	welcome := &chat{Name: name, Msg: encrypt("<joined>\n")}
	ec.Publish(subj, welcome)

	// Wait on new messages to send
	for {
		fmt.Printf("[%s] ", name)
		msg, _ := reader.ReadString('\n')
		ec.Publish(subj, chat{Name: name, Msg: encrypt(msg)})
	}

	runtime.Goexit()
}

func encrypt(msg string) []byte {
	plaintext := []byte(msg)
	return gcm.Seal(nil, nonce, plaintext, []byte(name))
}

func decrypt(msg *chat) string {
	v, err := gcm.Open(nil, nonce, msg.Msg, []byte(msg.Name))
	if err != nil {
		return "<unable to decrypt, wrong key?>\n"
	}
	return string(v)
}
<commit_msg>Move to server verified TLS, JSON payload with Base64<commit_after>// Copyright(c) 2015 Derek Collison (derek.collison@gmail.com)

package main

import (
	"bufio"
	"crypto/aes"
	"crypto/cipher"
	"crypto/sha256"
	"crypto/tls"
	"encoding/base64"
	"flag"
	"fmt"
	"log"
	"os"
	"os/signal"
	"runtime"

	"github.com/nats-io/nats"
)

const (
	AppName    = "nats_chat"
	Version    = "0.3"
	Proto      = "1"
	ServerName = "demo.nats.io"
	NatsUrl    = "nats://demo.nats.io:4443"
)

func usage() {
	log.Fatalf("Usage: nats-chat <subject> <key>\n")
}

// Hold name, key and subject
var name, subj, key string
var keyHash []byte

// Cipher
var gcm cipher.AEAD
var nonce []byte

// Messages we send
type chat struct {
	Name string `json:"name"`
	Msg  string `json:"encrypted_msg"`
}

func main() {
	log.SetFlags(0)
	flag.Usage = usage
	flag.Parse()

	args := flag.Args()
	if len(args) < 1 {
		usage()
	}

	subj, key = args[0], args[1]
	subj = fmt.Sprintf("snats.%s.%s", Proto, subj)

	h := sha256.New()
	h.Write([]byte(key))
	keyHash = h.Sum(nil)

	// Create cipher
	var err error
	block, err := aes.NewCipher(keyHash)
	if err != nil {
		log.Fatalf("Can't create cipher: %v\n", err)
	}
	gcm, err = cipher.NewGCMWithNonceSize(block, sha256.Size)
	if err != nil {
		log.Fatalf("Can't create gcm: %v\n", err)
	}

	// Generate the nonce
	h.Write([]byte(subj))
	h.Write(keyHash)
	nonce = h.Sum(nil)

	// Connect securely to NATS
	opts := nats.DefaultOptions
	opts.Name = AppName
	opts.Url = NatsUrl
	opts.Secure = true
	opts.TLSConfig = &tls.Config{
		ServerName: ServerName,
	}

	nc, err := opts.Connect()
	if err != nil {
		log.Fatalf("Got an error on Connect with Secure Options: %+v\n", err)
	}
	log.Printf("Securely connected to %s", NatsUrl)
	ec, _ := nats.NewEncodedConn(nc, nats.JSON_ENCODER)

	// Setup signal handlers to signal leaving.
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill)
	go func() {
		for range c {
			fmt.Printf("\n\n")
			if name != "" {
				exit := &chat{Name: name, Msg: encrypt("<left>\n")}
				ec.Publish(subj, exit)
				ec.Flush()
			}
			os.Exit(0)
		}
	}()

	// Collect the name
	fmt.Printf("Enter Name: ")
	fmt.Scanln(&name)

	// Create a reader for stdin
	reader := bufio.NewReader(os.Stdin)

	// Subscribe to messages
	ec.Subscribe(subj, func(msg *chat) {
		if msg.Name == name {
			return
		}
		fmt.Printf("\033[2K\r[%s] %s", msg.Name, decrypt(msg))
		fmt.Printf("[%s] ", name)
	})

	// Send welcome
	welcome := &chat{Name: name, Msg: encrypt("<joined>\n")}
	ec.Publish(subj, welcome)

	// Wait on new messages to send
	for {
		fmt.Printf("[%s] ", name)
		msg, _ := reader.ReadString('\n')
		ec.Publish(subj, chat{Name: name, Msg: encrypt(msg)})
	}

	runtime.Goexit()
}

func encrypt(msg string) string {
	plaintext := []byte(msg)
	ciphertext := gcm.Seal(nil, nonce, plaintext, []byte(name))
	return base64.StdEncoding.EncodeToString(ciphertext)
}

func decrypt(msg *chat) string {
	data, _ := base64.StdEncoding.DecodeString(msg.Msg)
	v, err := gcm.Open(nil, nonce, data, []byte(msg.Name))
	if err != nil {
		return "<unable to decrypt, wrong key?>\n"
	}
	return string(v)
}
<|endoftext|>


<commit_before>package skydb

import (
	"errors"
	"time"
)

// ErrUserDuplicated is returned by Conn.CreateUser when
// the UserInfo to be created has the same ID/username in the current container
var ErrUserDuplicated = errors.New("skydb: duplicated UserID or Username")

// ErrUserNotFound is returned by Conn.GetUser, Conn.UpdateUser and
// Conn.DeleteUser when the UserInfo's ID is not found
// in the current container
var ErrUserNotFound = errors.New("skydb: UserInfo ID not found")

// ErrDeviceNotFound is returned by Conn.GetDevice, Conn.DeleteDevice,
// Conn.DeleteDeviceByToken and Conn.DeleteEmptyDevicesByTime, if the desired Device
// cannot be found in the current container
var ErrDeviceNotFound = errors.New("skydb: Specific device not found")

// ZeroTime represent a zero time.Time. It is used in DeleteDeviceByToken and
// DeleteEmptyDevicesByTime to signify a Delete without time constraint.
var ZeroTime = time.Time{}

// DBHookFunc specifies the interface of a database hook function
type DBHookFunc func(Database, *Record, RecordHookEvent)

// QueryConfig provides optional parameters for queries.
// result is unlimited if Limit=0
type QueryConfig struct {
	Limit  uint64
	Offset uint64
}

// Conn encapsulates the interface of an Skygear connection to a container.
type Conn interface {
	// CRUD of UserInfo, smell like a bad design to attach these onto
	// a Conn, but looks very convenient to user.

	// CreateUser creates a new UserInfo in the container
	// this Conn associated to.
	CreateUser(userinfo *UserInfo) error

	// GetUser fetches the UserInfo with supplied ID in the container and
	// fills in the supplied UserInfo with the result.
	//
	// GetUser returns ErrUserNotFound if no UserInfo exists
	// for the supplied ID.
	GetUser(id string, userinfo *UserInfo) error

	// GetUserByUsernameEmail fetch the UserInfo with supplied username or email,
	//
	GetUserByUsernameEmail(username string, email string, userinfo *UserInfo) error

	// GetUserByPrincipalID fetches the UserInfo with supplied principal ID in the
	// container and fills in the supplied UserInfo with the result.
	//
	// Principal ID is an ID of an authenticated principal with such
	// authentication provided by AuthProvider.
	//
	// GetUserByPrincipalID returns ErrUserNotFound if no UserInfo exists
	// for the supplied principal ID.
	GetUserByPrincipalID(principalID string, userinfo *UserInfo) error

	// UpdateUser updates an existing UserInfo matched by the ID field.
	//
	// UpdateUser returns ErrUserNotFound if such UserInfo does not
	// exist in the container.
	UpdateUser(userinfo *UserInfo) error

	// QueryUser queries for UserInfo matching one of the specified emails.
	QueryUser(emails []string) ([]UserInfo, error)

	// DeleteUser removes UserInfo with the supplied ID in the container.
	//
	// DeleteUser returns ErrUserNotFound if such UserInfo does not
	// exist in the container.
	DeleteUser(id string) error

	// GetAsset retrieves Asset information by its name
	GetAsset(name string, asset *Asset) error

	// SaveAsset saves an Asset information into a container to
	// be referenced by records.
	SaveAsset(asset *Asset) error

	QueryRelation(user string, name string, direction string, config QueryConfig) []UserInfo
	QueryRelationCount(user string, name string, direction string) (uint64, error)
	AddRelation(user string, name string, targetUser string) error
	RemoveRelation(user string, name string, targetUser string) error

	GetDevice(id string, device *Device) error

	// QueryDevicesByUser queries the Device database which are registered
	// by the specified user.
	QueryDevicesByUser(user string) ([]Device, error)
	SaveDevice(device *Device) error
	DeleteDevice(id string) error

	// DeleteDeviceByToken deletes device where its Token == token and
	// LastRegisteredAt < t. If t == ZeroTime, LastRegisteredAt is not considered.
	//
	// If such device does not exist, ErrDeviceNotFound is returned.
	DeleteDeviceByToken(token string, t time.Time) error

	// DeleteEmptyDevicesByTime deletes device where Token is empty and
	// LastRegisteredAt < t. If t == ZeroTime, LastRegisteredAt is not considered.
	//
	// If such device does not exist, ErrDeviceNotFound is returned.
	DeleteEmptyDevicesByTime(t time.Time) error

	PublicDB() Database
	PrivateDB(userKey string) Database

	// Subscribe registers the specified recordEventChan to receive
	// RecordEvent from the Conn implementation
	Subscribe(recordEventChan chan RecordEvent) error

	Close() error
}

// AccessModel indicates the type of access control model while db query.
//go:generate stringer -type=AccessModel
type AccessModel int

const (
	RoleBaseAC AccessModel = iota + 1
	RelationBaseAC
)

// RecordHookEvent indicates the type of record event that triggered
// the hook
type RecordHookEvent int

// See the definition of RecordHookEvent
const (
	RecordCreated RecordHookEvent = iota + 1
	RecordUpdated
	RecordDeleted
)

// RecordEvent describes a change event on Record which is either
// Created, Updated or Deleted.
//
// For RecordCreated or RecordUpdated event, Record is the newly
// created / updated Record. For RecordDeleted, Record is the Record
// being deleted.
type RecordEvent struct {
	Record *Record
	Event  RecordHookEvent
}
<commit_msg>Add documentation regardind the Access Control Model<commit_after>package skydb

import (
	"errors"
	"time"
)

// ErrUserDuplicated is returned by Conn.CreateUser when
// the UserInfo to be created has the same ID/username in the current container
var ErrUserDuplicated = errors.New("skydb: duplicated UserID or Username")

// ErrUserNotFound is returned by Conn.GetUser, Conn.UpdateUser and
// Conn.DeleteUser when the UserInfo's ID is not found
// in the current container
var ErrUserNotFound = errors.New("skydb: UserInfo ID not found")

// ErrDeviceNotFound is returned by Conn.GetDevice, Conn.DeleteDevice,
// Conn.DeleteDeviceByToken and Conn.DeleteEmptyDevicesByTime, if the desired Device
// cannot be found in the current container
var ErrDeviceNotFound = errors.New("skydb: Specific device not found")

// ZeroTime represent a zero time.Time. It is used in DeleteDeviceByToken and
// DeleteEmptyDevicesByTime to signify a Delete without time constraint.
var ZeroTime = time.Time{}

// DBHookFunc specifies the interface of a database hook function
type DBHookFunc func(Database, *Record, RecordHookEvent)

// QueryConfig provides optional parameters for queries.
// result is unlimited if Limit=0
type QueryConfig struct {
	Limit  uint64
	Offset uint64
}

// Conn encapsulates the interface of an Skygear connection to a container.
type Conn interface {
	// CRUD of UserInfo, smell like a bad design to attach these onto
	// a Conn, but looks very convenient to user.

	// CreateUser creates a new UserInfo in the container
	// this Conn associated to.
	CreateUser(userinfo *UserInfo) error

	// GetUser fetches the UserInfo with supplied ID in the container and
	// fills in the supplied UserInfo with the result.
	//
	// GetUser returns ErrUserNotFound if no UserInfo exists
	// for the supplied ID.
	GetUser(id string, userinfo *UserInfo) error

	// GetUserByUsernameEmail fetch the UserInfo with supplied username or email,
	//
	GetUserByUsernameEmail(username string, email string, userinfo *UserInfo) error

	// GetUserByPrincipalID fetches the UserInfo with supplied principal ID in the
	// container and fills in the supplied UserInfo with the result.
	//
	// Principal ID is an ID of an authenticated principal with such
	// authentication provided by AuthProvider.
	//
	// GetUserByPrincipalID returns ErrUserNotFound if no UserInfo exists
	// for the supplied principal ID.
	GetUserByPrincipalID(principalID string, userinfo *UserInfo) error

	// UpdateUser updates an existing UserInfo matched by the ID field.
	//
	// UpdateUser returns ErrUserNotFound if such UserInfo does not
	// exist in the container.
	UpdateUser(userinfo *UserInfo) error

	// QueryUser queries for UserInfo matching one of the specified emails.
	QueryUser(emails []string) ([]UserInfo, error)

	// DeleteUser removes UserInfo with the supplied ID in the container.
	//
	// DeleteUser returns ErrUserNotFound if such UserInfo does not
	// exist in the container.
	DeleteUser(id string) error

	// GetAsset retrieves Asset information by its name
	GetAsset(name string, asset *Asset) error

	// SaveAsset saves an Asset information into a container to
	// be referenced by records.
	SaveAsset(asset *Asset) error

	QueryRelation(user string, name string, direction string, config QueryConfig) []UserInfo
	QueryRelationCount(user string, name string, direction string) (uint64, error)
	AddRelation(user string, name string, targetUser string) error
	RemoveRelation(user string, name string, targetUser string) error

	GetDevice(id string, device *Device) error

	// QueryDevicesByUser queries the Device database which are registered
	// by the specified user.
	QueryDevicesByUser(user string) ([]Device, error)
	SaveDevice(device *Device) error
	DeleteDevice(id string) error

	// DeleteDeviceByToken deletes device where its Token == token and
	// LastRegisteredAt < t. If t == ZeroTime, LastRegisteredAt is not considered.
	//
	// If such device does not exist, ErrDeviceNotFound is returned.
	DeleteDeviceByToken(token string, t time.Time) error

	// DeleteEmptyDevicesByTime deletes device where Token is empty and
	// LastRegisteredAt < t. If t == ZeroTime, LastRegisteredAt is not considered.
	//
	// If such device does not exist, ErrDeviceNotFound is returned.
	DeleteEmptyDevicesByTime(t time.Time) error

	PublicDB() Database
	PrivateDB(userKey string) Database

	// Subscribe registers the specified recordEventChan to receive
	// RecordEvent from the Conn implementation
	Subscribe(recordEventChan chan RecordEvent) error

	Close() error
}

// AccessModel indicates the type of access control model while db query.
//go:generate stringer -type=AccessModel
type AccessModel int

// RoleBaseAC is tranditional Access Control
// RelationBaseAC is Access Control determine by the user-user relation between
// creator and accessor
const (
	RoleBaseAC AccessModel = iota + 1
	RelationBaseAC
)

// RecordHookEvent indicates the type of record event that triggered
// the hook
type RecordHookEvent int

// See the definition of RecordHookEvent
const (
	RecordCreated RecordHookEvent = iota + 1
	RecordUpdated
	RecordDeleted
)

// RecordEvent describes a change event on Record which is either
// Created, Updated or Deleted.
//
// For RecordCreated or RecordUpdated event, Record is the newly
// created / updated Record. For RecordDeleted, Record is the Record
// being deleted.
type RecordEvent struct {
	Record *Record
	Event  RecordHookEvent
}
<|endoftext|>


<commit_before>package http

import (
	"github.com/astaxie/beego/orm"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"
)

type Nqm_node struct {
	Id         int
	Nid        string
	Pid        string
	Pname      string
	Pname_abbr string
	Cid        string
	Cname      string
	Cname_abbr string
	Iid        int
	Iname      string
	Status     int
	Note       string
	Addtime    int
}

func getNQMNodes(rw http.ResponseWriter, req *http.Request) {
	var nodes = make(map[string]interface{})
	errors := []string{}
	var result = make(map[string]interface{})
	result["error"] = errors
	items := []interface{}{}
	o := orm.NewOrm()
	o.Using("gz_nqm")
	var NQMNodes []*Nqm_node
	_, err := o.Raw("SELECT nid, pname, cname, status, note FROM gz_nqm.nqm_node ORDER BY nid ASC").QueryRows(&NQMNodes)
	if err != nil {
		setError(err.Error(), result)
	} else {
		for _, node := range NQMNodes {
			idc := map[string]string{
				"idc":      node.Nid,
				"status":   strconv.Itoa(node.Status),
				"province": node.Pname,
				"city":     node.Cname,
				"note":     node.Note,
			}
			items = append(items, idc)
		}
	}
	result["items"] = items
	nodes["result"] = result
	nodes["count"] = len(items)
	setResponse(rw, nodes)
}

func getLatestTimestamp(tableName string, result map[string]interface{}) string {
	timestamp := ""
	o := orm.NewOrm()
	o.Using("gz_nqm")
	sqlcmd := "SELECT mtime FROM gz_nqm." + tableName + " ORDER BY mtime DESC LIMIT 1"
	var rows []orm.Params
	num, err := o.Raw(sqlcmd).Values(&rows)
	if err != nil {
		setError(err.Error(), result)
	} else if num > 0 {
		timestamp = rows[0]["mtime"].(string)
	}
	return timestamp
}

func getSum(slice []float64) float64 {
	sum := float64(0)
	for _, number := range slice {
		sum += number
	}
	return sum
}

func getPacketLossAndAveragePingTime(tableName string, timestamp string, result map[string]interface{}) map[string]interface{} {
	idc := map[string]interface{}{}
	sends := []float64{}
	receives := []float64{}
	averages := []float64{}

	o := orm.NewOrm()
	o.Using("gz_nqm")
	sqlcmd := "SELECT send, receive, avg FROM gz_nqm." + tableName + " WHERE mtime = ?"
	var rows []orm.Params
	num, err := o.Raw(sqlcmd, timestamp).Values(&rows)
	if err != nil {
		setError(err.Error(), result)
	} else if num > 0 {
		for _, row := range rows {
			send, err := strconv.ParseFloat(row["send"].(string), 64)
			if err != nil {
				setError(err.Error(), result)
			} else {
				sends = append(sends, send)
			}
			receive, err := strconv.ParseFloat(row["receive"].(string), 64)
			if err != nil {
				setError(err.Error(), result)
			} else {
				receives = append(receives, receive)
			}
			avg, err := strconv.ParseFloat(row["avg"].(string), 64)
			if err != nil {
				setError(err.Error(), result)
			} else {
				averages = append(averages, avg)
			}
		}
	}
	divider := float64(len(sends))
	packetLossRate := 1 - (getSum(receives) / getSum(sends))
	averagePingTimeMilliseconds := getSum(averages) / divider

	timestampInt, err := strconv.Atoi(timestamp)
	if err != nil {
		setError(err.Error(), result)
	} else {
		idc = map[string]interface{}{
			"packetLossRate":              packetLossRate,
			"averagePingTimeMilliseconds": averagePingTimeMilliseconds,
			"time": time.Unix(int64(timestampInt), 0).Format("2006-01-02 15:04"),
		}
	}
	return idc
}

func getNQMPacketLoss(rw http.ResponseWriter, req *http.Request) {
	var nodes = make(map[string]interface{})
	errors := []string{}
	var result = make(map[string]interface{})
	result["error"] = errors
	items := []interface{}{}

	tablesMap := map[string]interface{}{}
	o := orm.NewOrm()
	o.Using("gz_nqm")
	sqlcmd := "SELECT table_name FROM information_schema.tables WHERE table_name LIKE 'nqm_log_prober_%'"
	var rows []orm.Params
	num, err := o.Raw(sqlcmd).Values(&rows)
	if err != nil {
		setError(err.Error(), result)
	} else if num > 0 {
		for _, row := range rows {
			tablesMap[row["table_name"].(string)] = ""
		}
	}

	nids := []string{}
	pidMap := map[string]interface{}{}
	var NQMNodes []*Nqm_node
	_, err = o.Raw("SELECT nid, pid, status FROM gz_nqm.nqm_node ORDER BY nid ASC").QueryRows(&NQMNodes)
	if err != nil {
		setError(err.Error(), result)
	} else {
		for _, node := range NQMNodes {
			if node.Status > 0 {
				nids = append(nids, node.Nid)
				pidMap[node.Nid] = node.Pid
			}
		}
	}
	idcNames := []string{}
	for _, nid := range nids {
		if _, ok := tablesMap["nqm_log_prober_"+strings.Replace(nid, "-", "_", -1)]; ok {
			idcNames = append(idcNames, nid)
		}
	}
	sort.Strings(idcNames)
	for _, idcName := range idcNames {
		tableName := "nqm_log_prober_" + strings.Replace(idcName, "-", "_", -1)
		timestamp := getLatestTimestamp(tableName, result)
		if timestamp != "" {
			idc := getPacketLossAndAveragePingTime(tableName, timestamp, result)
			idc["nodeName"] = idcName
			idc["pid"] = pidMap[idcName]
			items = append(items, idc)
		}
	}
	result["items"] = items
	nodes["result"] = result
	nodes["count"] = len(items)
	setResponse(rw, nodes)
}

func configNQMRoutes() {
	http.HandleFunc("/api/nqm/nodes", getNQMNodes)
	http.HandleFunc("/api/nqm/loss", getNQMPacketLoss)
}
<commit_msg>[OWL-632] add rw.Header().Set(Access-Control-Allow-Origin)<commit_after>package http

import (
	"github.com/astaxie/beego/orm"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"
)

type Nqm_node struct {
	Id         int
	Nid        string
	Pid        string
	Pname      string
	Pname_abbr string
	Cid        string
	Cname      string
	Cname_abbr string
	Iid        int
	Iname      string
	Status     int
	Note       string
	Addtime    int
}

func getNQMNodes(rw http.ResponseWriter, req *http.Request) {
	var nodes = make(map[string]interface{})
	errors := []string{}
	var result = make(map[string]interface{})
	result["error"] = errors
	items := []interface{}{}
	o := orm.NewOrm()
	o.Using("gz_nqm")
	var NQMNodes []*Nqm_node
	_, err := o.Raw("SELECT nid, pname, cname, status, note FROM gz_nqm.nqm_node ORDER BY nid ASC").QueryRows(&NQMNodes)
	if err != nil {
		setError(err.Error(), result)
	} else {
		for _, node := range NQMNodes {
			idc := map[string]string{
				"idc":      node.Nid,
				"status":   strconv.Itoa(node.Status),
				"province": node.Pname,
				"city":     node.Cname,
				"note":     node.Note,
			}
			items = append(items, idc)
		}
	}
	result["items"] = items
	nodes["result"] = result
	nodes["count"] = len(items)
	setResponse(rw, nodes)
}

func getLatestTimestamp(tableName string, result map[string]interface{}) string {
	timestamp := ""
	o := orm.NewOrm()
	o.Using("gz_nqm")
	sqlcmd := "SELECT mtime FROM gz_nqm." + tableName + " ORDER BY mtime DESC LIMIT 1"
	var rows []orm.Params
	num, err := o.Raw(sqlcmd).Values(&rows)
	if err != nil {
		setError(err.Error(), result)
	} else if num > 0 {
		timestamp = rows[0]["mtime"].(string)
	}
	return timestamp
}

func getSum(slice []float64) float64 {
	sum := float64(0)
	for _, number := range slice {
		sum += number
	}
	return sum
}

func getPacketLossAndAveragePingTime(tableName string, timestamp string, result map[string]interface{}) map[string]interface{} {
	idc := map[string]interface{}{}
	sends := []float64{}
	receives := []float64{}
	averages := []float64{}

	o := orm.NewOrm()
	o.Using("gz_nqm")
	sqlcmd := "SELECT send, receive, avg FROM gz_nqm." + tableName + " WHERE mtime = ?"
	var rows []orm.Params
	num, err := o.Raw(sqlcmd, timestamp).Values(&rows)
	if err != nil {
		setError(err.Error(), result)
	} else if num > 0 {
		for _, row := range rows {
			send, err := strconv.ParseFloat(row["send"].(string), 64)
			if err != nil {
				setError(err.Error(), result)
			} else {
				sends = append(sends, send)
			}
			receive, err := strconv.ParseFloat(row["receive"].(string), 64)
			if err != nil {
				setError(err.Error(), result)
			} else {
				receives = append(receives, receive)
			}
			avg, err := strconv.ParseFloat(row["avg"].(string), 64)
			if err != nil {
				setError(err.Error(), result)
			} else {
				averages = append(averages, avg)
			}
		}
	}
	divider := float64(len(sends))
	packetLossRate := 1 - (getSum(receives) / getSum(sends))
	averagePingTimeMilliseconds := getSum(averages) / divider

	timestampInt, err := strconv.Atoi(timestamp)
	if err != nil {
		setError(err.Error(), result)
	} else {
		idc = map[string]interface{}{
			"packetLossRate":              packetLossRate,
			"averagePingTimeMilliseconds": averagePingTimeMilliseconds,
			"time": time.Unix(int64(timestampInt), 0).Format("2006-01-02 15:04"),
		}
	}
	return idc
}

func getNQMPacketLoss(rw http.ResponseWriter, req *http.Request) {
	var nodes = make(map[string]interface{})
	errors := []string{}
	var result = make(map[string]interface{})
	result["error"] = errors
	items := []interface{}{}

	tablesMap := map[string]interface{}{}
	o := orm.NewOrm()
	o.Using("gz_nqm")
	sqlcmd := "SELECT table_name FROM information_schema.tables WHERE table_name LIKE 'nqm_log_prober_%'"
	var rows []orm.Params
	num, err := o.Raw(sqlcmd).Values(&rows)
	if err != nil {
		setError(err.Error(), result)
	} else if num > 0 {
		for _, row := range rows {
			tablesMap[row["table_name"].(string)] = ""
		}
	}

	nids := []string{}
	pidMap := map[string]interface{}{}
	var NQMNodes []*Nqm_node
	_, err = o.Raw("SELECT nid, pid, status FROM gz_nqm.nqm_node ORDER BY nid ASC").QueryRows(&NQMNodes)
	if err != nil {
		setError(err.Error(), result)
	} else {
		for _, node := range NQMNodes {
			if node.Status > 0 {
				nids = append(nids, node.Nid)
				pidMap[node.Nid] = node.Pid
			}
		}
	}
	idcNames := []string{}
	for _, nid := range nids {
		if _, ok := tablesMap["nqm_log_prober_"+strings.Replace(nid, "-", "_", -1)]; ok {
			idcNames = append(idcNames, nid)
		}
	}
	sort.Strings(idcNames)
	for _, idcName := range idcNames {
		tableName := "nqm_log_prober_" + strings.Replace(idcName, "-", "_", -1)
		timestamp := getLatestTimestamp(tableName, result)
		if timestamp != "" {
			idc := getPacketLossAndAveragePingTime(tableName, timestamp, result)
			idc["nodeName"] = idcName
			idc["pid"] = pidMap[idcName]
			items = append(items, idc)
		}
	}
	result["items"] = items
	nodes["result"] = result
	nodes["count"] = len(items)
	rw.Header().Set("Access-Control-Allow-Origin", "*")
	setResponse(rw, nodes)
}

func configNQMRoutes() {
	http.HandleFunc("/api/nqm/nodes", getNQMNodes)
	http.HandleFunc("/api/nqm/loss", getNQMPacketLoss)
}
<|endoftext|>


<commit_before>package xdebugproxy

import (
	"github.com/dfeyer/flow-debugproxy/config"
	"github.com/dfeyer/flow-debugproxy/logger"
	"github.com/dfeyer/flow-debugproxy/pathmapper"

	"fmt"
	"io"
	"net"
)

const h = "%s"

// Proxy represents a pair of connections and their state
type Proxy struct {
	sentBytes     uint64
	receivedBytes uint64
	Raddr         *net.TCPAddr
	Lconn, rconn  *net.TCPConn
	PathMapper    *pathmapper.PathMapper
	Config        *config.Config
	pipeErrors    chan error
}

func (p *Proxy) log(s string, args ...interface{}) {
	if p.Config.Verbose {
		logger.Info(s, args...)
	}
}

// Start the proxy
func (p *Proxy) Start() {
	defer p.Lconn.Close()

	// connect to remote
	rconn, err := net.DialTCP("tcp", nil, p.Raddr)
	if err != nil {
		p.log(h, "Unable to connect to your IDE, please check if your editor listen to incoming connection")
		p.log("Error message: %s", err)
		p.log(h, "Configure your IDE and reload the web page should solve this issue")
		p.log(h, "\nHit Ctrl-C to exit the proxy if don't need it ...")
		p.log(h, "\nYour fellow Umpa Lumpa")
		return
	}

	p.rconn = rconn
	defer p.rconn.Close()

	p.pipeErrors = make(chan error)
	defer close(p.pipeErrors)

	// display both ends
	p.log("Opened %s >>> %s", p.Lconn.RemoteAddr().String(), p.rconn.RemoteAddr().String())
	// bidirectional copy
	go p.pipe(p.Lconn, p.rconn)
	go p.pipe(p.rconn, p.Lconn)

	if err = <-p.pipeErrors; err != io.EOF {
		logger.Warn(h, err)
	}
	<-p.pipeErrors

	p.log("Closed (%d bytes sent, %d bytes recieved)", p.sentBytes, p.receivedBytes)
}

func (p *Proxy) pipe(src, dst *net.TCPConn) {
	// data direction
	var f, h string
	isFromDebugger := src == p.Lconn
	if isFromDebugger {
		f = "\nDebugger >>> IDE\n================"
	} else {
		f = "\nIDE >>> Debugger\n================"
	}
	// directional copy (64k buffer)
	buff := make([]byte, 0xffff)
	for {
		n, err := src.Read(buff)
		if err != nil {
			p.pipeErrors <- err
			// make sure the other pipe will stop as well
			dst.Close()
			return
		}
		b := buff[:n]
		p.log(h, f)
		if p.Config.VeryVerbose {
			if isFromDebugger {
				p.log("Raw protocol:\n%s\n", logger.Colorize(fmt.Sprintf(h, b), "blue"))
			} else {
				p.log("Raw protocol:\n%s\n", logger.Colorize(fmt.Sprintf(h, logger.FormatTextProtocol(b)), "blue"))
			}
		}
		// extract command name
		if isFromDebugger {
			b = p.PathMapper.ApplyMappingToXML(b)
		} else {
			b = p.PathMapper.ApplyMappingToTextProtocol(b)
		}
		// show output
		if p.Config.VeryVerbose {
			if isFromDebugger {
				p.log("Processed protocol:\n%s\n", logger.Colorize(fmt.Sprintf(h, b), "blue"))
			} else {
				p.log("Processed protocol:\n%s\n", logger.Colorize(fmt.Sprintf(h, logger.FormatTextProtocol(b)), "blue"))
			}
		} else {
			p.log(h, "")
		}
		// write out result
		n, err = dst.Write(b)
		if err != nil {
			p.pipeErrors <- err
			// make sure the other pipe will stop as well
			src.Close()
			return
		}
		if isFromDebugger {
			p.sentBytes += uint64(n)
		} else {
			p.receivedBytes += uint64(n)
		}
	}
}
<commit_msg>Revert "[TASK] Code cleanup"<commit_after>package xdebugproxy

import (
	"github.com/dfeyer/flow-debugproxy/config"
	"github.com/dfeyer/flow-debugproxy/logger"
	"github.com/dfeyer/flow-debugproxy/pathmapper"

	"fmt"
	"io"
	"net"
)

const h = "%s"

// Proxy represents a pair of connections and their state
type Proxy struct {
	sentBytes     uint64
	receivedBytes uint64
	Raddr         *net.TCPAddr
	Lconn, rconn  *net.TCPConn
	PathMapper    *pathmapper.PathMapper
	Config        *config.Config
	pipeErrors    chan error
}

func (p *Proxy) log(s string, args ...interface{}) {
	if p.Config.Verbose {
		logger.Info(s, args...)
	}
}

// Start the proxy
func (p *Proxy) Start() {
	defer p.Lconn.Close()

	// connect to remote
	rconn, err := net.DialTCP("tcp", nil, p.Raddr)
	if err != nil {
		p.log(h, "Unable to connect to your IDE, please check if your editor listen to incoming connection")
		p.log("Error message: %s", err)
		p.log(h, "Configure your IDE and reload the web page should solve this issue")
		p.log(h, "\nHit Ctrl-C to exit the proxy if don't need it ...")
		p.log(h, "\nYour fellow Umpa Lumpa")
		return
	}

	p.rconn = rconn
	defer p.rconn.Close()

	p.pipeErrors = make(chan error)
	defer close(p.pipeErrors)

	// display both ends
	p.log("Opened %s >>> %s", p.Lconn.RemoteAddr().String(), p.rconn.RemoteAddr().String())
	// bidirectional copy
	go p.pipe(p.Lconn, p.rconn)
	go p.pipe(p.rconn, p.Lconn)

	if err = <-p.pipeErrors; err != io.EOF {
		logger.Warn(h, err)
	}
	<-p.pipeErrors

	p.log("Closed (%d bytes sent, %d bytes recieved)", p.sentBytes, p.receivedBytes)
}

func (p *Proxy) pipe(src, dst *net.TCPConn) {
	// data direction
	var f, h string
	isFromDebugger := src == p.Lconn
	if isFromDebugger {
		f = "\nDebugger >>> IDE\n================"
	} else {
		f = "\nIDE >>> Debugger\n================"
	}
	h = "%s"
	// directional copy (64k buffer)
	buff := make([]byte, 0xffff)
	for {
		n, err := src.Read(buff)
		if err != nil {
			p.pipeErrors <- err
			// make sure the other pipe will stop as well
			dst.Close()
			return
		}
		b := buff[:n]
		p.log(h, f)
		if p.Config.VeryVerbose {
			if isFromDebugger {
				p.log("Raw protocol:\n%s\n", logger.Colorize(fmt.Sprintf(h, b), "blue"))
			} else {
				p.log("Raw protocol:\n%s\n", logger.Colorize(fmt.Sprintf(h, logger.FormatTextProtocol(b)), "blue"))
			}
		}
		// extract command name
		if isFromDebugger {
			b = p.PathMapper.ApplyMappingToXML(b)
		} else {
			b = p.PathMapper.ApplyMappingToTextProtocol(b)
		}
		// show output
		if p.Config.VeryVerbose {
			if isFromDebugger {
				p.log("Processed protocol:\n%s\n", logger.Colorize(fmt.Sprintf(h, b), "blue"))
			} else {
				p.log("Processed protocol:\n%s\n", logger.Colorize(fmt.Sprintf(h, logger.FormatTextProtocol(b)), "blue"))
			}
		} else {
			p.log(h, "")
		}
		// write out result
		n, err = dst.Write(b)
		if err != nil {
			p.pipeErrors <- err
			// make sure the other pipe will stop as well
			src.Close()
			return
		}
		if isFromDebugger {
			p.sentBytes += uint64(n)
		} else {
			p.receivedBytes += uint64(n)
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package v1

import (
	"strings"

	"github.com/go-macaron/binding"
	"gopkg.in/macaron.v1"

	api "code.gitea.io/sdk/gitea"

	"code.gitea.io/gitea/models"
	"code.gitea.io/gitea/modules/auth"
	"code.gitea.io/gitea/modules/context"
	"code.gitea.io/gitea/routers/api/v1/admin"
	"code.gitea.io/gitea/routers/api/v1/misc"
	"code.gitea.io/gitea/routers/api/v1/org"
	"code.gitea.io/gitea/routers/api/v1/repo"
	"code.gitea.io/gitea/routers/api/v1/user"
)

func repoAssignment() macaron.Handler {
	return func(ctx *context.APIContext) {
		userName := ctx.Params(":username")
		repoName := ctx.Params(":reponame")

		var (
			owner *models.User
			err   error
		)

		// Check if the user is the same as the repository owner.
		if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
			owner = ctx.User
		} else {
			owner, err = models.GetUserByName(userName)
			if err != nil {
				if models.IsErrUserNotExist(err) {
					ctx.Status(404)
				} else {
					ctx.Error(500, "GetUserByName", err)
				}
				return
			}
		}
		ctx.Repo.Owner = owner

		// Get repository.
		repo, err := models.GetRepositoryByName(owner.ID, repoName)
		if err != nil {
			if models.IsErrRepoNotExist(err) {
				ctx.Status(404)
			} else {
				ctx.Error(500, "GetRepositoryByName", err)
			}
			return
		} else if err = repo.GetOwner(); err != nil {
			ctx.Error(500, "GetOwner", err)
			return
		}

		if ctx.IsSigned && ctx.User.IsAdmin {
			ctx.Repo.AccessMode = models.AccessModeOwner
		} else {
			mode, err := models.AccessLevel(ctx.User, repo)
			if err != nil {
				ctx.Error(500, "AccessLevel", err)
				return
			}
			ctx.Repo.AccessMode = mode
		}

		if !ctx.Repo.HasAccess() {
			ctx.Status(404)
			return
		}

		ctx.Repo.Repository = repo
	}
}

// Contexter middleware already checks token for user sign in process.
func reqToken() macaron.Handler {
	return func(ctx *context.Context) {
		if !ctx.IsSigned {
			ctx.Error(401)
			return
		}
	}
}

func reqBasicAuth() macaron.Handler {
	return func(ctx *context.Context) {
		if !ctx.IsBasicAuth {
			ctx.Error(401)
			return
		}
	}
}

func reqAdmin() macaron.Handler {
	return func(ctx *context.Context) {
		if !ctx.IsSigned || !ctx.User.IsAdmin {
			ctx.Error(403)
			return
		}
	}
}

func reqRepoWriter() macaron.Handler {
	return func(ctx *context.Context) {
		if !ctx.Repo.IsWriter() {
			ctx.Error(403)
			return
		}
	}
}

func orgAssignment(args ...bool) macaron.Handler {
	var (
		assignOrg  bool
		assignTeam bool
	)
	if len(args) > 0 {
		assignOrg = args[0]
	}
	if len(args) > 1 {
		assignTeam = args[1]
	}
	return func(ctx *context.APIContext) {
		ctx.Org = new(context.APIOrganization)

		var err error
		if assignOrg {
			ctx.Org.Organization, err = models.GetUserByName(ctx.Params(":orgname"))
			if err != nil {
				if models.IsErrUserNotExist(err) {
					ctx.Status(404)
				} else {
					ctx.Error(500, "GetUserByName", err)
				}
				return
			}
		}

		if assignTeam {
			ctx.Org.Team, err = models.GetTeamByID(ctx.ParamsInt64(":teamid"))
			if err != nil {
				if models.IsErrUserNotExist(err) {
					ctx.Status(404)
				} else {
					ctx.Error(500, "GetTeamById", err)
				}
				return
			}
		}
	}
}

func mustEnableIssues(ctx *context.APIContext) {
	if !ctx.Repo.Repository.EnableIssues || ctx.Repo.Repository.EnableExternalTracker {
		ctx.Status(404)
		return
	}
}

func mustAllowPulls(ctx *context.Context) {
	if !ctx.Repo.Repository.AllowsPulls() {
		ctx.Status(404)
		return
	}
}

// RegisterRoutes registers all v1 APIs routes to web application.
// FIXME: custom form error response
func RegisterRoutes(m *macaron.Macaron) {
	bind := binding.Bind

	m.Group("/v1", func() {
		// Miscellaneous
		m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown)
		m.Post("/markdown/raw", misc.MarkdownRaw)

		// Users
		m.Group("/users", func() {
			m.Get("/search", user.Search)

			m.Group("/:username", func() {
				m.Get("", user.GetInfo)

				m.Group("/tokens", func() {
					m.Combo("").Get(user.ListAccessTokens).
						Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken)
				}, reqBasicAuth())
			})
		})

		m.Group("/users", func() {
			m.Group("/:username", func() {
				m.Get("/keys", user.ListPublicKeys)

				m.Get("/followers", user.ListFollowers)
				m.Group("/following", func() {
					m.Get("", user.ListFollowing)
					m.Get("/:target", user.CheckFollowing)
				})

				m.Get("/starred", user.GetStarredRepos)

				m.Get("/subscriptions", user.GetWatchedRepos)
			})
		}, reqToken())

		m.Group("/user", func() {
			m.Get("", user.GetAuthenticatedUser)
			m.Combo("/emails").Get(user.ListEmails).
				Post(bind(api.CreateEmailOption{}), user.AddEmail).
				Delete(bind(api.CreateEmailOption{}), user.DeleteEmail)

			m.Get("/followers", user.ListMyFollowers)
			m.Group("/following", func() {
				m.Get("", user.ListMyFollowing)
				m.Combo("/:username").Get(user.CheckMyFollowing).Put(user.Follow).Delete(user.Unfollow)
			})

			m.Group("/keys", func() {
				m.Combo("").Get(user.ListMyPublicKeys).
					Post(bind(api.CreateKeyOption{}), user.CreatePublicKey)
				m.Combo("/:id").Get(user.GetPublicKey).
					Delete(user.DeletePublicKey)
			})

			m.Group("/starred", func() {
				m.Get("", user.GetMyStarredRepos)
				m.Group("/:username/:reponame", func() {
					m.Get("", user.IsStarring)
					m.Put("", user.Star)
					m.Delete("", user.Unstar)
				}, context.ExtractOwnerAndRepo())
			})

			m.Get("/subscriptions", user.GetMyWatchedRepos)
		}, reqToken())

		// Repositories
		m.Combo("/user/repos", reqToken()).Get(repo.ListMyRepos).
			Post(bind(api.CreateRepoOption{}), repo.Create)
		m.Post("/org/:org/repos", reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo)

		m.Group("/repos", func() {
			m.Get("/search", repo.Search)
		})

		m.Combo("/repositories/:id", reqToken()).Get(repo.GetByID)

		m.Group("/repos", func() {
			m.Post("/migrate", bind(auth.MigrateRepoForm{}), repo.Migrate)
			m.Combo("/:username/:reponame", context.ExtractOwnerAndRepo()).
				Get(repo.Get).
				Delete(repo.Delete)

			m.Group("/:username/:reponame", func() {
				m.Group("/hooks", func() {
					m.Combo("").Get(repo.ListHooks).
						Post(bind(api.CreateHookOption{}), repo.CreateHook)
					m.Combo("/:id").Get(repo.GetHook).
						Patch(bind(api.EditHookOption{}), repo.EditHook).
						Delete(repo.DeleteHook)
				})
				m.Group("/collaborators", func() {
					m.Get("", repo.ListCollaborators)
					m.Combo("/:collaborator").Get(repo.IsCollaborator).
						Put(bind(api.AddCollaboratorOption{}), repo.AddCollaborator).
						Delete(repo.DeleteCollaborator)
				})
				m.Get("/raw/*", context.RepoRef(), repo.GetRawFile)
				m.Get("/archive/*", repo.GetArchive)
				m.Group("/branches", func() {
					m.Get("", repo.ListBranches)
					m.Get("/:branchname", repo.GetBranch)
				})
				m.Group("/keys", func() {
					m.Combo("").Get(repo.ListDeployKeys).
						Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey)
					m.Combo("/:id").Get(repo.GetDeployKey).
						Delete(repo.DeleteDeploykey)
				})
				m.Group("/issues", func() {
					m.Combo("").Get(repo.ListIssues).Post(bind(api.CreateIssueOption{}), repo.CreateIssue)
					m.Group("/comments", func() {
						m.Get("", repo.ListRepoIssueComments)
						m.Combo("/:id").Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueComment)
					})
					m.Group("/:index", func() {
						m.Combo("").Get(repo.GetIssue).Patch(bind(api.EditIssueOption{}), repo.EditIssue)

						m.Group("/comments", func() {
							m.Combo("").Get(repo.ListIssueComments).Post(bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
							m.Combo("/:id").Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueComment).
								Delete(repo.DeleteIssueComment)
						})

						m.Group("/labels", func() {
							m.Combo("").Get(repo.ListIssueLabels).
								Post(bind(api.IssueLabelsOption{}), repo.AddIssueLabels).
								Put(bind(api.IssueLabelsOption{}), repo.ReplaceIssueLabels).
								Delete(repo.ClearIssueLabels)
							m.Delete("/:id", repo.DeleteIssueLabel)
						})

					})
				}, mustEnableIssues)
				m.Group("/labels", func() {
					m.Combo("").Get(repo.ListLabels).
						Post(bind(api.CreateLabelOption{}), repo.CreateLabel)
					m.Combo("/:id").Get(repo.GetLabel).Patch(bind(api.EditLabelOption{}), repo.EditLabel).
						Delete(repo.DeleteLabel)
				})
				m.Group("/milestones", func() {
					m.Combo("").Get(repo.ListMilestones).
						Post(reqRepoWriter(), bind(api.CreateMilestoneOption{}), repo.CreateMilestone)
					m.Combo("/:id").Get(repo.GetMilestone).
						Patch(reqRepoWriter(), bind(api.EditMilestoneOption{}), repo.EditMilestone).
						Delete(reqRepoWriter(), repo.DeleteMilestone)
				})
				m.Group("/subscription", func() {
					m.Get("", user.IsWatching)
					m.Put("", user.Watch)
					m.Delete("", user.Unwatch)
				}, context.ExtractOwnerAndRepo())
				m.Get("/editorconfig/:filename", context.RepoRef(), repo.GetEditorconfig)
				m.Group("/pulls", func() {
					m.Combo("").Get(bind(api.ListPullRequestsOptions{}), repo.ListPullRequests).Post(reqRepoWriter(), bind(api.CreatePullRequestOption{}), repo.CreatePullRequest)
					m.Group("/:index", func() {
						m.Combo("").Get(repo.GetPullRequest).Patch(reqRepoWriter(), bind(api.EditPullRequestOption{}), repo.EditPullRequest)
						m.Combo("/merge").Get(repo.IsPullRequestMerged).Post(reqRepoWriter(), repo.MergePullRequest)
					})

				}, mustAllowPulls, context.ReferencesGitRepo())
			}, repoAssignment())
		}, reqToken())

		// Organizations
		m.Get("/user/orgs", reqToken(), org.ListMyOrgs)
		m.Get("/users/:username/orgs", org.ListUserOrgs)
		m.Group("/orgs/:orgname", func() {
			m.Combo("").Get(org.Get).Patch(bind(api.EditOrgOption{}), org.Edit)
			m.Combo("/teams").Get(org.ListTeams)
			m.Group("/hooks", func() {
				m.Combo("").Get(org.ListHooks).
					Post(bind(api.CreateHookOption{}), org.CreateHook)
				m.Combo("/:id").Get(org.GetHook).
					Patch(bind(api.EditHookOption{}), org.EditHook).
					Delete(org.DeleteHook)
			})
		}, orgAssignment(true))
		m.Group("/teams/:teamid", func() {
			m.Get("", org.GetTeam)
			m.Get("/members", org.GetTeamMembers)
			m.Get("/repos", org.GetTeamRepos)
		}, orgAssignment(false, true))

		m.Any("/*", func(ctx *context.Context) {
			ctx.Error(404)
		})

		m.Group("/admin", func() {
			m.Group("/users", func() {
				m.Post("", bind(api.CreateUserOption{}), admin.CreateUser)

				m.Group("/:username", func() {
					m.Combo("").Patch(bind(api.EditUserOption{}), admin.EditUser).
						Delete(admin.DeleteUser)
					m.Post("/keys", bind(api.CreateKeyOption{}), admin.CreatePublicKey)
					m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg)
					m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo)
				})
			})

			m.Group("/orgs/:orgname", func() {
				m.Group("/teams", func() {
					m.Post("", orgAssignment(true), bind(api.CreateTeamOption{}), admin.CreateTeam)
				})
			})
			m.Group("/teams", func() {
				m.Group("/:teamid", func() {
					m.Combo("").Patch(bind(api.EditTeamOption{}), admin.EditTeam).
						Delete(admin.DeleteTeam)
					m.Combo("/members/:username").Put(admin.AddTeamMember).Delete(admin.RemoveTeamMember)
					m.Combo("/repos/:reponame").Put(admin.AddTeamRepository).Delete(admin.RemoveTeamRepository)
				}, orgAssignment(false, true))
			})
		}, reqAdmin())
	}, context.APIContexter())
}
<commit_msg>Permissions bug fix for webhooks<commit_after>// Copyright 2015 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package v1

import (
	"strings"

	"github.com/go-macaron/binding"
	"gopkg.in/macaron.v1"

	api "code.gitea.io/sdk/gitea"

	"code.gitea.io/gitea/models"
	"code.gitea.io/gitea/modules/auth"
	"code.gitea.io/gitea/modules/context"
	"code.gitea.io/gitea/routers/api/v1/admin"
	"code.gitea.io/gitea/routers/api/v1/misc"
	"code.gitea.io/gitea/routers/api/v1/org"
	"code.gitea.io/gitea/routers/api/v1/repo"
	"code.gitea.io/gitea/routers/api/v1/user"
)

func repoAssignment() macaron.Handler {
	return func(ctx *context.APIContext) {
		userName := ctx.Params(":username")
		repoName := ctx.Params(":reponame")

		var (
			owner *models.User
			err   error
		)

		// Check if the user is the same as the repository owner.
		if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
			owner = ctx.User
		} else {
			owner, err = models.GetUserByName(userName)
			if err != nil {
				if models.IsErrUserNotExist(err) {
					ctx.Status(404)
				} else {
					ctx.Error(500, "GetUserByName", err)
				}
				return
			}
		}
		ctx.Repo.Owner = owner

		// Get repository.
		repo, err := models.GetRepositoryByName(owner.ID, repoName)
		if err != nil {
			if models.IsErrRepoNotExist(err) {
				ctx.Status(404)
			} else {
				ctx.Error(500, "GetRepositoryByName", err)
			}
			return
		} else if err = repo.GetOwner(); err != nil {
			ctx.Error(500, "GetOwner", err)
			return
		}

		if ctx.IsSigned && ctx.User.IsAdmin {
			ctx.Repo.AccessMode = models.AccessModeOwner
		} else {
			mode, err := models.AccessLevel(ctx.User, repo)
			if err != nil {
				ctx.Error(500, "AccessLevel", err)
				return
			}
			ctx.Repo.AccessMode = mode
		}

		if !ctx.Repo.HasAccess() {
			ctx.Status(404)
			return
		}

		ctx.Repo.Repository = repo
	}
}

// Contexter middleware already checks token for user sign in process.
func reqToken() macaron.Handler {
	return func(ctx *context.Context) {
		if !ctx.IsSigned {
			ctx.Error(401)
			return
		}
	}
}

func reqBasicAuth() macaron.Handler {
	return func(ctx *context.Context) {
		if !ctx.IsBasicAuth {
			ctx.Error(401)
			return
		}
	}
}

func reqAdmin() macaron.Handler {
	return func(ctx *context.Context) {
		if !ctx.IsSigned || !ctx.User.IsAdmin {
			ctx.Error(403)
			return
		}
	}
}

func reqRepoWriter() macaron.Handler {
	return func(ctx *context.Context) {
		if !ctx.Repo.IsWriter() {
			ctx.Error(403)
			return
		}
	}
}

func orgAssignment(args ...bool) macaron.Handler {
	var (
		assignOrg  bool
		assignTeam bool
	)
	if len(args) > 0 {
		assignOrg = args[0]
	}
	if len(args) > 1 {
		assignTeam = args[1]
	}
	return func(ctx *context.APIContext) {
		ctx.Org = new(context.APIOrganization)

		var err error
		if assignOrg {
			ctx.Org.Organization, err = models.GetUserByName(ctx.Params(":orgname"))
			if err != nil {
				if models.IsErrUserNotExist(err) {
					ctx.Status(404)
				} else {
					ctx.Error(500, "GetUserByName", err)
				}
				return
			}
		}

		if assignTeam {
			ctx.Org.Team, err = models.GetTeamByID(ctx.ParamsInt64(":teamid"))
			if err != nil {
				if models.IsErrUserNotExist(err) {
					ctx.Status(404)
				} else {
					ctx.Error(500, "GetTeamById", err)
				}
				return
			}
		}
	}
}

func mustEnableIssues(ctx *context.APIContext) {
	if !ctx.Repo.Repository.EnableIssues || ctx.Repo.Repository.EnableExternalTracker {
		ctx.Status(404)
		return
	}
}

func mustAllowPulls(ctx *context.Context) {
	if !ctx.Repo.Repository.AllowsPulls() {
		ctx.Status(404)
		return
	}
}

// RegisterRoutes registers all v1 APIs routes to web application.
// FIXME: custom form error response
func RegisterRoutes(m *macaron.Macaron) {
	bind := binding.Bind

	m.Group("/v1", func() {
		// Miscellaneous
		m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown)
		m.Post("/markdown/raw", misc.MarkdownRaw)

		// Users
		m.Group("/users", func() {
			m.Get("/search", user.Search)

			m.Group("/:username", func() {
				m.Get("", user.GetInfo)

				m.Group("/tokens", func() {
					m.Combo("").Get(user.ListAccessTokens).
						Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken)
				}, reqBasicAuth())
			})
		})

		m.Group("/users", func() {
			m.Group("/:username", func() {
				m.Get("/keys", user.ListPublicKeys)

				m.Get("/followers", user.ListFollowers)
				m.Group("/following", func() {
					m.Get("", user.ListFollowing)
					m.Get("/:target", user.CheckFollowing)
				})

				m.Get("/starred", user.GetStarredRepos)

				m.Get("/subscriptions", user.GetWatchedRepos)
			})
		}, reqToken())

		m.Group("/user", func() {
			m.Get("", user.GetAuthenticatedUser)
			m.Combo("/emails").Get(user.ListEmails).
				Post(bind(api.CreateEmailOption{}), user.AddEmail).
				Delete(bind(api.CreateEmailOption{}), user.DeleteEmail)

			m.Get("/followers", user.ListMyFollowers)
			m.Group("/following", func() {
				m.Get("", user.ListMyFollowing)
				m.Combo("/:username").Get(user.CheckMyFollowing).Put(user.Follow).Delete(user.Unfollow)
			})

			m.Group("/keys", func() {
				m.Combo("").Get(user.ListMyPublicKeys).
					Post(bind(api.CreateKeyOption{}), user.CreatePublicKey)
				m.Combo("/:id").Get(user.GetPublicKey).
					Delete(user.DeletePublicKey)
			})

			m.Group("/starred", func() {
				m.Get("", user.GetMyStarredRepos)
				m.Group("/:username/:reponame", func() {
					m.Get("", user.IsStarring)
					m.Put("", user.Star)
					m.Delete("", user.Unstar)
				}, context.ExtractOwnerAndRepo())
			})

			m.Get("/subscriptions", user.GetMyWatchedRepos)
		}, reqToken())

		// Repositories
		m.Combo("/user/repos", reqToken()).Get(repo.ListMyRepos).
			Post(bind(api.CreateRepoOption{}), repo.Create)
		m.Post("/org/:org/repos", reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo)

		m.Group("/repos", func() {
			m.Get("/search", repo.Search)
		})

		m.Combo("/repositories/:id", reqToken()).Get(repo.GetByID)

		m.Group("/repos", func() {
			m.Post("/migrate", bind(auth.MigrateRepoForm{}), repo.Migrate)
			m.Combo("/:username/:reponame", context.ExtractOwnerAndRepo()).
				Get(repo.Get).
				Delete(repo.Delete)

			m.Group("/:username/:reponame", func() {
				m.Group("/hooks", func() {
					m.Combo("").Get(repo.ListHooks).
						Post(bind(api.CreateHookOption{}), repo.CreateHook)
					m.Combo("/:id").Get(repo.GetHook).
						Patch(bind(api.EditHookOption{}), repo.EditHook).
						Delete(repo.DeleteHook)
				}, reqRepoWriter())
				m.Group("/collaborators", func() {
					m.Get("", repo.ListCollaborators)
					m.Combo("/:collaborator").Get(repo.IsCollaborator).
						Put(bind(api.AddCollaboratorOption{}), repo.AddCollaborator).
						Delete(repo.DeleteCollaborator)
				})
				m.Get("/raw/*", context.RepoRef(), repo.GetRawFile)
				m.Get("/archive/*", repo.GetArchive)
				m.Group("/branches", func() {
					m.Get("", repo.ListBranches)
					m.Get("/:branchname", repo.GetBranch)
				})
				m.Group("/keys", func() {
					m.Combo("").Get(repo.ListDeployKeys).
						Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey)
					m.Combo("/:id").Get(repo.GetDeployKey).
						Delete(repo.DeleteDeploykey)
				})
				m.Group("/issues", func() {
					m.Combo("").Get(repo.ListIssues).Post(bind(api.CreateIssueOption{}), repo.CreateIssue)
					m.Group("/comments", func() {
						m.Get("", repo.ListRepoIssueComments)
						m.Combo("/:id").Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueComment)
					})
					m.Group("/:index", func() {
						m.Combo("").Get(repo.GetIssue).Patch(bind(api.EditIssueOption{}), repo.EditIssue)

						m.Group("/comments", func() {
							m.Combo("").Get(repo.ListIssueComments).Post(bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
							m.Combo("/:id").Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueComment).
								Delete(repo.DeleteIssueComment)
						})

						m.Group("/labels", func() {
							m.Combo("").Get(repo.ListIssueLabels).
								Post(bind(api.IssueLabelsOption{}), repo.AddIssueLabels).
								Put(bind(api.IssueLabelsOption{}), repo.ReplaceIssueLabels).
								Delete(repo.ClearIssueLabels)
							m.Delete("/:id", repo.DeleteIssueLabel)
						})

					})
				}, mustEnableIssues)
				m.Group("/labels", func() {
					m.Combo("").Get(repo.ListLabels).
						Post(bind(api.CreateLabelOption{}), repo.CreateLabel)
					m.Combo("/:id").Get(repo.GetLabel).Patch(bind(api.EditLabelOption{}), repo.EditLabel).
						Delete(repo.DeleteLabel)
				})
				m.Group("/milestones", func() {
					m.Combo("").Get(repo.ListMilestones).
						Post(reqRepoWriter(), bind(api.CreateMilestoneOption{}), repo.CreateMilestone)
					m.Combo("/:id").Get(repo.GetMilestone).
						Patch(reqRepoWriter(), bind(api.EditMilestoneOption{}), repo.EditMilestone).
						Delete(reqRepoWriter(), repo.DeleteMilestone)
				})
				m.Group("/subscription", func() {
					m.Get("", user.IsWatching)
					m.Put("", user.Watch)
					m.Delete("", user.Unwatch)
				}, context.ExtractOwnerAndRepo())
				m.Get("/editorconfig/:filename", context.RepoRef(), repo.GetEditorconfig)
				m.Group("/pulls", func() {
					m.Combo("").Get(bind(api.ListPullRequestsOptions{}), repo.ListPullRequests).Post(reqRepoWriter(), bind(api.CreatePullRequestOption{}), repo.CreatePullRequest)
					m.Group("/:index", func() {
						m.Combo("").Get(repo.GetPullRequest).Patch(reqRepoWriter(), bind(api.EditPullRequestOption{}), repo.EditPullRequest)
						m.Combo("/merge").Get(repo.IsPullRequestMerged).Post(reqRepoWriter(), repo.MergePullRequest)
					})

				}, mustAllowPulls, context.ReferencesGitRepo())
			}, repoAssignment())
		}, reqToken())

		// Organizations
		m.Get("/user/orgs", reqToken(), org.ListMyOrgs)
		m.Get("/users/:username/orgs", org.ListUserOrgs)
		m.Group("/orgs/:orgname", func() {
			m.Combo("").Get(org.Get).Patch(bind(api.EditOrgOption{}), org.Edit)
			m.Combo("/teams").Get(org.ListTeams)
			m.Group("/hooks", func() {
				m.Combo("").Get(org.ListHooks).
					Post(bind(api.CreateHookOption{}), org.CreateHook)
				m.Combo("/:id").Get(org.GetHook).
					Patch(bind(api.EditHookOption{}), org.EditHook).
					Delete(org.DeleteHook)
			})
		}, orgAssignment(true))
		m.Group("/teams/:teamid", func() {
			m.Get("", org.GetTeam)
			m.Get("/members", org.GetTeamMembers)
			m.Get("/repos", org.GetTeamRepos)
		}, orgAssignment(false, true))

		m.Any("/*", func(ctx *context.Context) {
			ctx.Error(404)
		})

		m.Group("/admin", func() {
			m.Group("/users", func() {
				m.Post("", bind(api.CreateUserOption{}), admin.CreateUser)

				m.Group("/:username", func() {
					m.Combo("").Patch(bind(api.EditUserOption{}), admin.EditUser).
						Delete(admin.DeleteUser)
					m.Post("/keys", bind(api.CreateKeyOption{}), admin.CreatePublicKey)
					m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg)
					m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo)
				})
			})

			m.Group("/orgs/:orgname", func() {
				m.Group("/teams", func() {
					m.Post("", orgAssignment(true), bind(api.CreateTeamOption{}), admin.CreateTeam)
				})
			})
			m.Group("/teams", func() {
				m.Group("/:teamid", func() {
					m.Combo("").Patch(bind(api.EditTeamOption{}), admin.EditTeam).
						Delete(admin.DeleteTeam)
					m.Combo("/members/:username").Put(admin.AddTeamMember).Delete(admin.RemoveTeamMember)
					m.Combo("/repos/:reponame").Put(admin.AddTeamRepository).Delete(admin.RemoveTeamRepository)
				}, orgAssignment(false, true))
			})
		}, reqAdmin())
	}, context.APIContexter())
}
<|endoftext|>