Skip to content

Commit

Permalink
add issue version to prevent change issue simultaneously
Browse files Browse the repository at this point in the history
  • Loading branch information
metiftikci committed May 22, 2024
1 parent c6cf96d commit c7433b3
Show file tree
Hide file tree
Showing 15 changed files with 89 additions and 17 deletions.
3 changes: 3 additions & 0 deletions models/issues/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ func (err ErrIssueWasClosed) Error() string {
return fmt.Sprintf("Issue [%d] %d was already closed", err.ID, err.Index)
}

var ErrIssueAlreadyChanged = util.NewInvalidArgumentErrorf("the issue is already changed")

// Issue represents an issue or pull request of repository.
type Issue struct {
ID int64 `xorm:"pk autoincr"`
Expand Down Expand Up @@ -121,6 +123,7 @@ type Issue struct {
NumComments int
Ref string
PinOrder int `xorm:"DEFAULT 0"`
Version int `xorm:"NOT NULL DEFAULT 0"`

DeadlineUnix timeutil.TimeStamp `xorm:"INDEX"`

Expand Down
12 changes: 9 additions & 3 deletions models/issues/issue_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ func UpdateIssueAttachments(ctx context.Context, issueID int64, uuids []string)
}

// ChangeIssueContent changes issue content, as the given user.
func ChangeIssueContent(ctx context.Context, issue *Issue, doer *user_model.User, content string) (err error) {
func ChangeIssueContent(ctx context.Context, issue *Issue, doer *user_model.User, content string, version int) (err error) {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
Expand All @@ -254,9 +254,15 @@ func ChangeIssueContent(ctx context.Context, issue *Issue, doer *user_model.User
}

issue.Content = content
issue.Version++

if err = UpdateIssueCols(ctx, issue, "content"); err != nil {
return fmt.Errorf("UpdateIssueCols: %w", err)
affected, err := db.GetEngine(ctx).ID(issue.ID).Where("Version = ?", version).Cols("Content", "Version").Update(issue)
if err != nil {
return err
}

if affected == 0 {
return ErrIssueAlreadyChanged
}

if err = SaveIssueContentHistory(ctx, doer.ID, issue.ID, 0,
Expand Down
4 changes: 4 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"code.gitea.io/gitea/models/migrations/v1_20"
"code.gitea.io/gitea/models/migrations/v1_21"
"code.gitea.io/gitea/models/migrations/v1_22"
"code.gitea.io/gitea/models/migrations/v1_23"
"code.gitea.io/gitea/models/migrations/v1_6"
"code.gitea.io/gitea/models/migrations/v1_7"
"code.gitea.io/gitea/models/migrations/v1_8"
Expand Down Expand Up @@ -587,6 +588,9 @@ var migrations = []Migration{
NewMigration("Drop wrongly created table o_auth2_application", v1_22.DropWronglyCreatedTable),

// Gitea 1.22.0-rc1 ends at 299

// v299 -> v300
NewMigration("Add version to issue table", v1_23.AddVersionToIssue),
}

// GetCurrentDBVersion returns the current db version
Expand Down
25 changes: 25 additions & 0 deletions models/migrations/v1_23/v299.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_23 //nolint

import "xorm.io/xorm"

func AddVersionToIssue(x *xorm.Engine) error {
type Issue struct {
Version int `xorm:"NOT NULL DEFAULT 0"`
}

sess := x.NewSession()
defer sess.Close()

if err := sess.Begin(); err != nil {
return err
}

if err := sess.Sync(new(Issue)); err != nil {
return err
}

return sess.Commit()
}
7 changes: 4 additions & 3 deletions modules/structs/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,10 @@ type CreateIssueOption struct {

// EditIssueOption options for editing an issue
type EditIssueOption struct {
Title string `json:"title"`
Body *string `json:"body"`
Ref *string `json:"ref"`
Title string `json:"title"`
Version int `json:"version"`
Body *string `json:"body"`
Ref *string `json:"ref"`
// deprecated
Assignee *string `json:"assignee"`
Assignees []string `json:"assignees"`
Expand Down
1 change: 1 addition & 0 deletions modules/structs/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ type CreatePullRequestOption struct {
// EditPullRequestOption options when modify pull request
type EditPullRequestOption struct {
Title string `json:"title"`
Version int `json:"version"`
Body *string `json:"body"`
Base string `json:"base"`
Assignee string `json:"assignee"`
Expand Down
1 change: 1 addition & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1443,6 +1443,7 @@ issues.new.clear_assignees = Clear assignees
issues.new.no_assignees = No Assignees
issues.new.no_reviewers = No reviewers
issues.new.blocked_user = Cannot create issue because you are blocked by the repository owner.
issues.edit.already_changed = Unable to change issue content because issue is already changed
issues.edit.blocked_user = Cannot edit content because you are blocked by the poster or repository owner.
issues.choose.get_started = Get Started
issues.choose.open_external_link = Open
Expand Down
11 changes: 8 additions & 3 deletions routers/api/v1/repo/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -810,10 +810,15 @@ func EditIssue(ctx *context.APIContext) {
}
}
if form.Body != nil {
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body)
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, form.Version)
if err != nil {
ctx.Error(http.StatusInternalServerError, "ChangeContent", err)
return
if errors.Is(err, issues_model.ErrIssueAlreadyChanged) {
ctx.Error(http.StatusBadRequest, "ChangeContent", err)
return
} else {

Check failure on line 818 in routers/api/v1/repo/issue.go

View workflow job for this annotation

GitHub Actions / lint-backend

indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (revive)

Check failure on line 818 in routers/api/v1/repo/issue.go

View workflow job for this annotation

GitHub Actions / lint-go-gogit

indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (revive)

Check failure on line 818 in routers/api/v1/repo/issue.go

View workflow job for this annotation

GitHub Actions / lint-go-windows

indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (revive)
ctx.Error(http.StatusInternalServerError, "ChangeContent", err)
return
}
}
}
if form.Ref != nil {
Expand Down
2 changes: 1 addition & 1 deletion routers/api/v1/repo/issue_attachment.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ func CreateIssueAttachment(ctx *context.APIContext) {

issue.Attachments = append(issue.Attachments, attachment)

if err := issue_service.ChangeContent(ctx, issue, ctx.Doer, issue.Content); err != nil {
if err := issue_service.ChangeContent(ctx, issue, ctx.Doer, issue.Content, issue.Version); err != nil {
ctx.Error(http.StatusInternalServerError, "ChangeContent", err)
return
}
Expand Down
11 changes: 8 additions & 3 deletions routers/api/v1/repo/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -610,10 +610,15 @@ func EditPullRequest(ctx *context.APIContext) {
}
}
if form.Body != nil {
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body)
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, form.Version)
if err != nil {
ctx.Error(http.StatusInternalServerError, "ChangeContent", err)
return
if errors.Is(err, issues_model.ErrIssueAlreadyChanged) {
ctx.Error(http.StatusBadRequest, "ChangeContent", err)
return
} else {

Check failure on line 618 in routers/api/v1/repo/pull.go

View workflow job for this annotation

GitHub Actions / lint-backend

indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (revive)

Check failure on line 618 in routers/api/v1/repo/pull.go

View workflow job for this annotation

GitHub Actions / lint-go-gogit

indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (revive)

Check failure on line 618 in routers/api/v1/repo/pull.go

View workflow job for this annotation

GitHub Actions / lint-go-windows

indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (revive)
ctx.Error(http.StatusInternalServerError, "ChangeContent", err)
return
}
}
}

Expand Down
4 changes: 3 additions & 1 deletion routers/web/repo/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -2247,9 +2247,11 @@ func UpdateIssueContent(ctx *context.Context) {
return
}

if err := issue_service.ChangeContent(ctx, issue, ctx.Doer, ctx.Req.FormValue("content")); err != nil {
if err := issue_service.ChangeContent(ctx, issue, ctx.Doer, ctx.Req.FormValue("content"), ctx.FormInt("version")); err != nil {
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.JSONError(ctx.Tr("repo.issues.edit.blocked_user"))
} else if errors.Is(err, issues_model.ErrIssueAlreadyChanged) {
ctx.JSONError(ctx.Tr("repo.issues.edit.already_changed"))
} else {
ctx.ServerError("ChangeContent", err)
}
Expand Down
4 changes: 2 additions & 2 deletions services/issue/content.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
)

// ChangeContent changes issue content, as the given user.
func ChangeContent(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, content string) error {
func ChangeContent(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, content string, version int) error {
if err := issue.LoadRepo(ctx); err != nil {
return err
}
Expand All @@ -26,7 +26,7 @@ func ChangeContent(ctx context.Context, issue *issues_model.Issue, doer *user_mo

oldContent := issue.Content

if err := issues_model.ChangeIssueContent(ctx, issue, doer, content); err != nil {
if err := issues_model.ChangeIssueContent(ctx, issue, doer, content, version); err != nil {
return err
}

Expand Down
2 changes: 1 addition & 1 deletion templates/repo/issue/view_content.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
{{end}}
</div>
<div id="issue-{{.Issue.ID}}-raw" class="raw-content tw-hidden">{{.Issue.Content}}</div>
<div class="edit-content-zone tw-hidden" data-update-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/content" data-context="{{.RepoLink}}" data-attachment-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/attachments" data-view-attachment-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/view-attachments"></div>
<div class="edit-content-zone tw-hidden" data-update-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/content" data-version="{{.Issue.Version}}" data-context="{{.RepoLink}}" data-attachment-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/attachments" data-view-attachment-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/view-attachments"></div>
{{if .Issue.Attachments}}
{{template "repo/issue/view_content/attachments" dict "Attachments" .Issue.Attachments "RenderedContent" .Issue.RenderedContent}}
{{end}}
Expand Down
10 changes: 10 additions & 0 deletions templates/swagger/v1_json.tmpl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions web_src/js/features/repo-issue-edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {handleReply} from './repo-issue.js';
import {getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.js';
import {createDropzone} from './dropzone.js';
import {GET, POST} from '../modules/fetch.js';
import {showErrorToast} from '../modules/toast.js';
import {hideElem, showElem} from '../utils/dom.js';
import {attachRefIssueContextPopup} from './contextpopup.js';
import {initCommentContent, initMarkupContent} from '../markup/content.js';
Expand Down Expand Up @@ -121,14 +122,22 @@ async function onEditContent(event) {
hideElem(editContentZone);
const dropzoneInst = comboMarkdownEditor.attachedDropzoneInst;
try {
const version = editContentZone.getAttribute('data-version');

const params = new URLSearchParams({
content: comboMarkdownEditor.value(),
context: editContentZone.getAttribute('data-context'),
version,
});
for (const fileInput of dropzoneInst?.element.querySelectorAll('.files [name=files]')) params.append('files[]', fileInput.value);

const response = await POST(editContentZone.getAttribute('data-update-url'), {data: params});
const data = await response.json();
if (response.status === 400) {
showErrorToast(data.errorMessage);
return;
}
editContentZone.setAttribute('data-version', parseInt(version) + 1);
if (!data.content) {
renderContent.innerHTML = document.getElementById('no-content').innerHTML;
rawContent.textContent = '';
Expand Down

0 comments on commit c7433b3

Please sign in to comment.