1
1
mirror of https://github.com/go-gitea/gitea synced 2025-07-23 02:38:35 +00:00

fix(issue): Replace stopwatch toggle with explicit start/stop actions (#34818)

This PR fixes a state de-synchronization bug with the issue stopwatch,
it resolves the issue by replacing the ambiguous `/toggle` endpoint
with two explicit endpoints: `/start` and `/stop`.

- The "Start timer" button now exclusively calls the `/start` endpoint.
- The "Stop timer" button now exclusively calls the `/stop` endpoint.

This ensures the user's intent is clearly communicated to the server,
eliminating the state inconsistency and fixing the bug.

---------

Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Junsik Kong
2025-06-25 08:22:58 +09:00
committed by GitHub
parent 63fb25382b
commit 0e629c545a
13 changed files with 162 additions and 204 deletions

View File

@@ -4,7 +4,6 @@
package repo
import (
"errors"
"net/http"
issues_model "code.gitea.io/gitea/models/issues"
@@ -49,14 +48,17 @@ func StartIssueStopwatch(ctx *context.APIContext) {
// "409":
// description: Cannot start a stopwatch again if it already exists
issue, err := prepareIssueStopwatch(ctx, false)
if err != nil {
issue := prepareIssueForStopwatch(ctx)
if ctx.Written() {
return
}
if err := issues_model.CreateIssueStopwatch(ctx, ctx.Doer, issue); err != nil {
if ok, err := issues_model.CreateIssueStopwatch(ctx, ctx.Doer, issue); err != nil {
ctx.APIErrorInternal(err)
return
} else if !ok {
ctx.APIError(http.StatusConflict, "cannot start a stopwatch again if it already exists")
return
}
ctx.Status(http.StatusCreated)
@@ -96,18 +98,20 @@ func StopIssueStopwatch(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
// "409":
// description: Cannot stop a non existent stopwatch
// description: Cannot stop a non-existent stopwatch
issue, err := prepareIssueStopwatch(ctx, true)
if err != nil {
issue := prepareIssueForStopwatch(ctx)
if ctx.Written() {
return
}
if err := issues_model.FinishIssueStopwatch(ctx, ctx.Doer, issue); err != nil {
if ok, err := issues_model.FinishIssueStopwatch(ctx, ctx.Doer, issue); err != nil {
ctx.APIErrorInternal(err)
return
} else if !ok {
ctx.APIError(http.StatusConflict, "cannot stop a non-existent stopwatch")
return
}
ctx.Status(http.StatusCreated)
}
@@ -145,22 +149,25 @@ func DeleteIssueStopwatch(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
// "409":
// description: Cannot cancel a non existent stopwatch
// description: Cannot cancel a non-existent stopwatch
issue, err := prepareIssueStopwatch(ctx, true)
if err != nil {
issue := prepareIssueForStopwatch(ctx)
if ctx.Written() {
return
}
if err := issues_model.CancelStopwatch(ctx, ctx.Doer, issue); err != nil {
if ok, err := issues_model.CancelStopwatch(ctx, ctx.Doer, issue); err != nil {
ctx.APIErrorInternal(err)
return
} else if !ok {
ctx.APIError(http.StatusConflict, "cannot cancel a non-existent stopwatch")
return
}
ctx.Status(http.StatusNoContent)
}
func prepareIssueStopwatch(ctx *context.APIContext, shouldExist bool) (*issues_model.Issue, error) {
func prepareIssueForStopwatch(ctx *context.APIContext) *issues_model.Issue {
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
@@ -168,32 +175,19 @@ func prepareIssueStopwatch(ctx *context.APIContext, shouldExist bool) (*issues_m
} else {
ctx.APIErrorInternal(err)
}
return nil, err
return nil
}
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
ctx.Status(http.StatusForbidden)
return nil, errors.New("Unable to write to PRs")
return nil
}
if !ctx.Repo.CanUseTimetracker(ctx, issue, ctx.Doer) {
ctx.Status(http.StatusForbidden)
return nil, errors.New("Cannot use time tracker")
return nil
}
if issues_model.StopwatchExists(ctx, ctx.Doer.ID, issue.ID) != shouldExist {
if shouldExist {
ctx.APIError(http.StatusConflict, "cannot stop/cancel a non existent stopwatch")
err = errors.New("cannot stop/cancel a non existent stopwatch")
} else {
ctx.APIError(http.StatusConflict, "cannot start a stopwatch again if it already exists")
err = errors.New("cannot start a stopwatch again if it already exists")
}
return nil, err
}
return issue, nil
return issue
}
// GetStopwatches get all stopwatches