review

package
v0.0.0-...-92b63b8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 7, 2019 License: MIT Imports: 16 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var Accept = auth.Required(func(w http.ResponseWriter, r *http.Request) {
	user, err := auth.UserFromRequest(r)
	if err != nil {
		logrus.Errorf("Error while getting user from request context: %+v", err)
		utils.Error(w, utils.InternalErrorResponse("No authorized user for this request"))
	}

	vars := mux.Vars(r)
	reviewID, err := strconv.Atoi(vars["id"])
	if err != nil {
		logrus.Warnf("Incorrect ID: %s, error: %+v", vars["id"], err)
		utils.Error(w, utils.JSONErrorResponse{
			Status:        http.StatusNotFound,
			Message:       "No review with id: " + vars["id"],
			ClientMessage: "Не удалось найти ревью",
		})
		return
	}
	review, err := store.Reviews.FindReviewByID(reviewID)
	if err != nil {
		logrus.Warnf("Cannot find review: %s, error: %+v", vars["id"], err)
		utils.Error(w, utils.JSONErrorResponse{
			Status:        http.StatusNotFound,
			Message:       "No review with id: " + vars["id"],
			ClientMessage: "Не удалось найти ревью",
		})
		return
	}
	if review.Owner == user.Login || !hasAccess(user.Login, review) {
		logrus.Warnf("User %s has no access to review %d", user.Login, review.ID)
		utils.Error(w, utils.JSONErrorResponse{
			Status:        http.StatusForbidden,
			Message:       "No access to this review",
			ClientMessage: "У вас недостаточно прав для закрытия ревью",
		})
		return
	}

	review.Closed = true
	review.Accepted = true
	err = store.Reviews.UpdateReview(&review)
	if err != nil {
		logrus.Errorf("Cannot save review: %+v", err)
		utils.Error(w, utils.InternalErrorResponse("Cannot update review"))
		return
	}
})

Accept review

View Source
var Decline = auth.Required(func(w http.ResponseWriter, r *http.Request) {
	user, err := auth.UserFromRequest(r)
	if err != nil {
		logrus.Errorf("Error while getting user from request context: %+v", err)
		utils.Error(w, utils.InternalErrorResponse("No authorized user for this request"))
	}

	vars := mux.Vars(r)
	reviewID, err := strconv.Atoi(vars["id"])
	if err != nil {
		logrus.Warnf("Incorrect ID: %s, error: %+v", vars["id"], err)
		utils.Error(w, utils.JSONErrorResponse{
			Status:        http.StatusNotFound,
			Message:       "No review with id: " + vars["id"],
			ClientMessage: "Не удалось найти ревью",
		})
		return
	}
	review, err := store.Reviews.FindReviewByID(reviewID)
	if err != nil {
		logrus.Warnf("Cannot find review: %d, error: %+v", reviewID, err)
		utils.Error(w, utils.JSONErrorResponse{
			Status:        http.StatusNotFound,
			Message:       "No review with id: " + vars["id"],
			ClientMessage: "Не удалось найти ревью",
		})
		return
	}
	if !hasAccess(user.Login, review) {
		logrus.Warnf("User %s has no access to review %d", user.Login, review.ID)
		utils.Error(w, utils.JSONErrorResponse{
			Status:        http.StatusForbidden,
			Message:       "No access to this review",
			ClientMessage: "У вас недостаточно прав для закрытия ревью",
		})
		return
	}

	review.Closed = true
	review.Accepted = false
	err = store.Reviews.UpdateReview(&review)
	if err != nil {
		logrus.Errorf("Cannot save review: %+v", err)
		utils.Error(w, utils.InternalErrorResponse("Cannot update review"))
		return
	}
})

Decline review

View Source
var IncomingReviews = auth.Required(func(w http.ResponseWriter, r *http.Request) {
	user, err := auth.UserFromRequest(r)
	if err != nil {
		logrus.Errorf("Error while getting user from request context: %+v", err)
		utils.Error(w, utils.InternalErrorResponse("No authorized user for this request"))
		return
	}

	reviews, err := store.Reviews.FindReviewsByReviewer(user.Login)
	if err != nil {
		logrus.Errorf("Cannot load outgoing reviews: %+v", err)
		utils.Error(w, utils.InternalErrorResponse("Cannot load incoming reviews"))
		return
	}
	res, err := newAPIReviews(reviews)
	if err != nil {
		logrus.Errorf("Cannot load users from incoming reviews: %+v", err)
		utils.Error(w, utils.InternalErrorResponse("Cannot load incoming reviews"))
		return
	}
	utils.Ok(w, res)
})

IncomingReviews return reviews

View Source
var NewReview = auth.Required(func(w http.ResponseWriter, r *http.Request) {
	user, err := auth.UserFromRequest(r)
	if err != nil {
		logrus.Errorf("Error while getting user from request context: %+v", err)
		utils.Error(w, utils.InternalErrorResponse("No authorized user for this request"))
		return
	}

	var form struct {
		Name        string `json:"name" validate:"required"`
		Reviewers   string `json:"reviewers" validate:"required"`
		FileName    string `json:"file_name" validate:"required"`
		FileContent string `json:"file_content" validate:"required"`
	}
	if err := utils.UnmarshalForm(w, r, &form); err != nil {
		return
	}

	fileContent, err := base64.StdEncoding.DecodeString(form.FileContent)
	if err != nil {
		logrus.Warnf("Incorrect base64 file content: %s, error: %+v", form.FileContent, err)
		utils.Error(w, utils.JSONErrorResponse{
			Status:        http.StatusBadRequest,
			Message:       "Incorrect base64 file content",
			ClientMessage: "Некорректная кодировка файла",
		})
		return
	}

	reviewers := strings.Split(form.Reviewers, ",")
	for _, r := range reviewers {
		_, err := store.Auth.FindUserByLogin(r)
		if err != nil {
			logrus.Infof("Incorrect list of reviewers: %+v", err)
			utils.Error(w, utils.JSONErrorResponse{
				Status:        http.StatusNotAcceptable,
				Message:       "Incorrect list of reviewers",
				ClientMessage: "Некорректный список ревьюеров",
			})
			return
		}
	}

	file := NewVersionedFile(form.FileName, difflib.SplitLines(string(fileContent)))
	bytesFile, err := json.Marshal(&file)
	if err != nil {
		logrus.Errorf("Cannot serialize versioned file: %+v", err)
		utils.Error(w, utils.InternalErrorResponse("Cannot create versioned file"))
		return
	}
	review := store.Review{
		File:      bytesFile,
		Name:      form.Name,
		Owner:     user.Login,
		Reviewers: reviewers,
		Updated:   time.Now().Unix(),
		Closed:    false,
		Accepted:  false,
	}

	err = store.Reviews.CreateReview(&review)
	if err != nil {
		logrus.Errorf("Cannot save new review: %+v", err)
		utils.Error(w, utils.InternalErrorResponse("Cannot save review"))
	}
	utils.Ok(w, nil)
})

NewReview creates new review

View Source
var OutgoingReviews = auth.Required(func(w http.ResponseWriter, r *http.Request) {
	user, err := auth.UserFromRequest(r)
	if err != nil {
		logrus.Errorf("Error while getting user from request context: %+v", err)
		utils.Error(w, utils.InternalErrorResponse("No authorized user for this request"))
		return
	}

	reviews, err := store.Reviews.FindReviewsByOwner(user.Login)
	if err != nil {
		logrus.Errorf("Cannot load outgoing reviews: %+v", err)
		utils.Error(w, utils.InternalErrorResponse("Cannot load outgoing reviews"))
		return
	}
	res, err := newAPIReviews(reviews)
	if err != nil {
		logrus.Errorf("Cannot load users from outgoing reviews: %+v", err)
		utils.Error(w, utils.InternalErrorResponse("Cannot load outgoing reviews"))
		return
	}
	utils.Ok(w, res)
})

OutgoingReviews returns reviews

View Source
var Review = auth.Required(func(w http.ResponseWriter, r *http.Request) {
	user, err := auth.UserFromRequest(r)
	if err != nil {
		logrus.Errorf("Error while getting user from request context: %+v", err)
		utils.Error(w, utils.InternalErrorResponse("No authorized user for this request"))
		return
	}

	vars := mux.Vars(r)
	reviewID, err := strconv.Atoi(vars["id"])
	if err != nil {
		logrus.Warnf("Incorrect ID: %+s, error: %+v", vars["id"], err)
		utils.Error(w, utils.JSONErrorResponse{
			Status:        http.StatusNotFound,
			Message:       "No review with id: " + vars["id"],
			ClientMessage: "Не удалось найти ревью",
		})
		return
	}
	review, err := store.Reviews.FindReviewByID(reviewID)
	if err != nil {
		logrus.Warnf("Cannot find review with id: %+s, error: %+v", vars["id"], err)
		utils.Error(w, utils.JSONErrorResponse{
			Status:        http.StatusNotFound,
			Message:       "No review with id: " + vars["id"],
			ClientMessage: "Не удалось найти ревью",
		})
		return
	}
	var file VersionedFile
	err = json.Unmarshal(review.File, &file)
	if err != nil {
		logrus.Errorf("Error while deserializing versioned file: %+v", err)
		utils.Error(w, utils.InternalErrorResponse("Cannot deserialize versioned file"))
		return
	}

	getParamOr := func(param string, def int) (int, error) {
		value := r.URL.Query()[param]
		if len(value) > 0 {
			return strconv.Atoi(value[0])
		}
		return def, nil
	}

	startRev, err := getParamOr("start_rev", 0)
	if err != nil {
		logrus.Warnf("Incorrect start revision: %+v", err)
		utils.Error(w, utils.JSONErrorResponse{
			Status:        http.StatusBadRequest,
			Message:       "Incorrect start revision",
			ClientMessage: "Некорректный номер начальной ревизии",
		})
		return
	}
	endRev, err := getParamOr("end_rev", file.RevisionsCount()-1)
	if err != nil {
		logrus.Warnf("Incorrect end revision: %+v", err)
		utils.Error(w, utils.JSONErrorResponse{
			Status:        http.StatusBadRequest,
			Message:       "Incorrect end revision",
			ClientMessage: "Некорректный номер конечной ревизии",
		})
		return
	}

	if !hasAccess(user.Login, review) {
		logrus.Warnf("User %s han no access to review %d", user.Login, review.ID)
		utils.Error(w, utils.JSONErrorResponse{
			Status:        http.StatusForbidden,
			Message:       "No access to this review",
			ClientMessage: "У вас недостаточно прав для просмотра ревью",
		})
		return
	}

	content, err := file.Diff(startRev, endRev)
	if err != nil {
		logrus.Errorf("Cannot calculate diff: %+v", err)
		utils.Error(w, utils.InternalErrorResponse("Cannot calculate diff"))
		return
	}

	comments, err := store.Comments.CommentsForReview(review.ID)
	if err != nil {
		logrus.Errorf("Cannot load comments for review: %d, error: %+v", review.ID, err)
		utils.Error(w, utils.InternalErrorResponse("Cannot load comments"))
		return
	}
	apiComments := make(map[int]*APIComment)
	for _, comment := range comments {
		ac, err := NewAPIComment(comment)
		if err != nil {
			logrus.Errorf("Cannot create API comment for review: %d, error: %+v", review.ID, err)
			utils.Error(w, utils.InternalErrorResponse("Cannot load comments"))
			return
		}
		if comment.ParentID == 0 {
			apiComments[comment.ID] = &ac
		} else {
			parent, exists := apiComments[comment.ParentID]
			if !exists {
				logrus.Errorf("Cannot find parent comment for review: %d, error: %+v", review.ID, err)
				utils.Error(w, utils.InternalErrorResponse("Cannot load comments"))
				return
			}
			parent.Childs = append(parent.Childs, &ac)
			apiComments[comment.ParentID] = parent
			apiComments[comment.ID] = &ac
		}
	}
	resComments := make([]APIComment, 0)
	for _, comment := range comments {
		if comment.ParentID == 0 {
			resComments = append(resComments, *apiComments[comment.ID])
		}
	}

	res, err := NewAPIReview(review)
	if err != nil {
		logrus.Errorf("Cannot load users from incoming reviews: %+v", err)
		utils.Error(w, utils.InternalErrorResponse("Cannot load incoming reviews"))
		return
	}
	utils.Ok(w, &map[string]interface{}{
		"info":     res,
		"diff":     content,
		"comments": resComments,
	})
})

Review returns information about review

View Source
var SearchReviewer = auth.Required(func(w http.ResponseWriter, r *http.Request) {
	user, err := auth.UserFromRequest(r)
	if err != nil {
		logrus.Errorf("Error while getting user from request context: %+v", err)
		utils.Error(w, utils.InternalErrorResponse("No authorized user for this request"))
	}

	query, ok := r.URL.Query()["query"]
	if !ok || len(query) == 0 || len(query[0]) == 0 {
		logrus.Warnf("Empty query for search")
		utils.Error(w, utils.JSONErrorResponse{
			Status:        http.StatusBadRequest,
			Message:       "Empty Query",
			ClientMessage: "Пустой запрос для поиска",
		})
		return
	}

	results, err := store.Auth.FindUsers(query[0], user.Login)
	if err != nil {
		logrus.Errorf("Cannot find reviewers: %+v", err)
		utils.Error(w, utils.JSONErrorResponse{
			Status:        http.StatusInternalServerError,
			Message:       "Cannot find users",
			ClientMessage: "Не удалось произвести поиск пользователей",
		})
		return
	}
	res := make([]auth.APIUser, 0)
	for _, r := range results {
		res = append(res, auth.NewAPIUser(r))
	}
	utils.Ok(w, res)
})

SearchReviewer by login or by name

View Source
var UpdateReview = auth.Required(func(w http.ResponseWriter, r *http.Request) {
	user, err := auth.UserFromRequest(r)
	if err != nil {
		logrus.Errorf("Error while getting user from request context: %+v", err)
		utils.Error(w, utils.InternalErrorResponse("No authorized user for this request"))
		return
	}

	vars := mux.Vars(r)
	reviewID, err := strconv.Atoi(vars["id"])
	if err != nil {
		logrus.Warnf("Incorrect ID: %s, error: %+v", vars["id"], err)
		utils.Error(w, utils.JSONErrorResponse{
			Status:        http.StatusNotFound,
			Message:       "No review with id: " + vars["id"],
			ClientMessage: "Не удалось найти ревью",
		})
		return
	}
	review, err := store.Reviews.FindReviewByID(reviewID)
	if err != nil {
		logrus.Warnf("Cannot find review: %s, error: %+v", vars["id"], err)
		utils.Error(w, utils.JSONErrorResponse{
			Status:        http.StatusNotFound,
			Message:       "No review with id: " + vars["id"],
			ClientMessage: "Не удалось найти ревью",
		})
		return
	}
	if review.Owner != user.Login {
		logrus.Warnf("User %s tries to update review %d without being the creator", user.Login, review.ID)
		utils.Error(w, utils.JSONErrorResponse{
			Status:        http.StatusForbidden,
			Message:       "Only owner allowed to update review",
			ClientMessage: "Только автор ревью может его обновлять",
		})
		return
	}

	var form struct {
		Name        string `json:"name" validate:"required"`
		Reviewers   string `json:"reviewers" validate:"required"`
		NewRevision string `json:"new_revision"`
	}
	if err := utils.UnmarshalForm(w, r, &form); err != nil {
		return
	}

	fileContent, err := base64.StdEncoding.DecodeString(form.NewRevision)
	if err != nil {
		logrus.Warnf("Incorrect base64 file content: %s, error: %+v", form.NewRevision, err)
		utils.Error(w, utils.JSONErrorResponse{
			Status:        http.StatusBadRequest,
			Message:       "Incorrect base64 file content",
			ClientMessage: "Некорректная кодировка файла",
		})
		return
	}
	review.Name = form.Name

	reviewers := strings.Split(form.Reviewers, ",")
	for _, r := range reviewers {
		_, err := store.Auth.FindUserByLogin(r)
		if err != nil {
			logrus.Infof("Incorrect list of reviewers: %+v", err)
			utils.Error(w, utils.JSONErrorResponse{
				Status:        http.StatusNotAcceptable,
				Message:       "Incorrect list of reviewers",
				ClientMessage: "Некорректный список ревьюеров",
			})
			return
		}
	}
	review.Reviewers = reviewers

	var file VersionedFile
	err = json.Unmarshal(review.File, &file)
	if err != nil {
		logrus.Errorf("Error while deserializing versioned file: %+v", err)
		utils.Error(w, utils.InternalErrorResponse("Cannot deserialize versioned file"))
		return
	}
	if len(form.NewRevision) > 0 {
		err = file.AddRevision(difflib.SplitLines(string(fileContent)))
		if err != nil {
			logrus.Errorf("Cannot add revision: %+v", err)
			utils.Error(w, utils.InternalErrorResponse("Cannot add revision"))
			return
		}
		review.Updated = time.Now().Unix()
	}
	bytesFile, err := json.Marshal(&file)
	if err != nil {
		logrus.Errorf("Cannot serialize versioned file: %+v", err)
		utils.Error(w, utils.InternalErrorResponse("Cannot create versioned file"))
		return
	}
	review.File = bytesFile
	err = store.Reviews.UpdateReview(&review)
	if err != nil {
		logrus.Errorf("Cannot save updated review: %+v", err)
		utils.Error(w, utils.InternalErrorResponse("Cannot update review"))
		return
	}
	utils.Ok(w, nil)
})

UpdateReview information or add revision

Functions

This section is empty.

Types

type APIComment

type APIComment struct {
	ID      int           `json:"id"`
	Author  auth.APIUser  `json:"author"`
	Created int64         `json:"created"`
	Text    string        `json:"text"`
	LineID  string        `json:"line_id"`
	Childs  []*APIComment `json:"childs"`
}

APIComment represents api result struct

func NewAPIComment

func NewAPIComment(comment store.Comment) (APIComment, error)

NewAPIComment creates new api comment from store comment

type APIReview

type APIReview struct {
	ID             int            `json:"id"`
	Name           string         `json:"name"`
	Updated        int64          `json:"updated"`
	Closed         bool           `json:"closed"`
	Accepted       bool           `json:"accepted"`
	Owner          auth.APIUser   `json:"owner"`
	Reviewers      []auth.APIUser `json:"reviewers"`
	RevisionsCount int            `json:"revisions_count"`
	CommentsCount  int            `json:"comments_count"`
}

APIReview represents api result struct

func NewAPIReview

func NewAPIReview(review store.Review) (APIReview, error)

NewAPIReview creates new api review from store review

type Diff

type Diff struct {
	FileName string      `json:"filename"`
	Groups   []DiffGroup `json:"groups"`
}

Diff represents diff between revisions

func (Diff) String

func (diff Diff) String() string

type DiffGroup

type DiffGroup struct {
	OldRange diffRange  `json:"old_range"`
	NewRange diffRange  `json:"new_range"`
	Lines    []DiffLine `json:"lines"`
}

DiffGroup represents part of final diff

func (DiffGroup) String

func (group DiffGroup) String() string

type DiffLine

type DiffLine struct {
	Type DiffType `json:"-"`
	Old  *Line    `json:"old"`
	New  *Line    `json:"new"`
}

DiffLine represents line in diff output

func (DiffLine) MarshalJSON

func (diffLine DiffLine) MarshalJSON() ([]byte, error)

MarshalJSON with corrected diff byte

type DiffType

type DiffType int

DiffType represents type of operation for particular line in diff

const (
	// NoOperation - content similar in both files
	NoOperation DiffType = iota
	// InsertOperation - new inserted line
	InsertOperation DiffType = iota
	// DeleteOperation - line is removed from old file
	DeleteOperation DiffType = iota
)

type File

type File struct {
	Lines []Line
}

File represents lines of file with revision numbers

func (File) Content

func (file File) Content() string

Content returns content of revisioned file

type Line

type Line struct {
	Content  string `json:"content"`
	Revision int    `json:"revision"`
	ID       string `json:"id"`
}

Line is a line in file with number of revision

type VersionedFile

type VersionedFile struct {
	Name      string
	Revisions []File
}

VersionedFile represents file with all it's revisions

func NewVersionedFile

func NewVersionedFile(name string, content []string) VersionedFile

NewVersionedFile constructs versioned file from content of original file

func (*VersionedFile) AddRevision

func (file *VersionedFile) AddRevision(content []string) error

AddRevision adds new revision to the versioned file

func (*VersionedFile) Diff

func (file *VersionedFile) Diff(revision1, revision2 int) (Diff, error)

Diff returns diff between two revisions as string

func (*VersionedFile) GetRevision

func (file *VersionedFile) GetRevision(revision int) (File, error)

GetRevision returns specified revision of file

func (VersionedFile) RevisionsCount

func (file VersionedFile) RevisionsCount() int

RevisionsCount returns count of revisions (includes original file)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL