diff --git a/.dockerignore b/.dockerignore index 98ef52233..4c14a9462 100644 --- a/.dockerignore +++ b/.dockerignore @@ -14,7 +14,7 @@ _test # MS VSCode .vscode -__debug_bin +__debug_bin* # Architecture specific extensions/prefixes *.[568vq] diff --git a/.eslintrc.yaml b/.eslintrc.yaml index eeb3e20cb..99ce2e97d 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -167,7 +167,7 @@ rules: "@stylistic/js/semi-spacing": [2, {before: false, after: true}] "@stylistic/js/semi-style": [2, last] "@stylistic/js/space-before-blocks": [2, always] - "@stylistic/js/space-before-function-paren": [0] + "@stylistic/js/space-before-function-paren": [2, {anonymous: ignore, named: never, asyncArrow: always}] "@stylistic/js/space-in-parens": [2, never] "@stylistic/js/space-infix-ops": [2] "@stylistic/js/space-unary-ops": [2] @@ -281,7 +281,7 @@ rules: jquery/no-ajax-events: [2] jquery/no-ajax: [2] jquery/no-animate: [2] - jquery/no-attr: [0] + jquery/no-attr: [2] jquery/no-bind: [2] jquery/no-class: [0] jquery/no-clone: [2] @@ -303,7 +303,7 @@ rules: jquery/no-in-array: [2] jquery/no-is-array: [2] jquery/no-is-function: [2] - jquery/no-is: [0] + jquery/no-is: [2] jquery/no-load: [2] jquery/no-map: [2] jquery/no-merge: [2] @@ -397,7 +397,7 @@ rules: no-jquery/no-animate-toggle: [2] no-jquery/no-animate: [2] no-jquery/no-append-html: [2] - no-jquery/no-attr: [0] + no-jquery/no-attr: [2] no-jquery/no-bind: [2] no-jquery/no-box-model: [2] no-jquery/no-browser: [2] @@ -440,7 +440,7 @@ rules: no-jquery/no-is-numeric: [2] no-jquery/no-is-plain-object: [2] no-jquery/no-is-window: [2] - no-jquery/no-is: [0] + no-jquery/no-is: [2] no-jquery/no-jquery-constructor: [0] no-jquery/no-live: [2] no-jquery/no-load-shorthand: [2] diff --git a/Makefile b/Makefile index 70a7f906e..984cc0ca6 100644 --- a/Makefile +++ b/Makefile @@ -458,7 +458,8 @@ lint-actions: $(GO) run $(ACTIONLINT_PACKAGE) .PHONY: lint-templates -lint-templates: .venv +lint-templates: .venv node_modules + @node tools/lint-templates-svg.js @poetry run djlint $(shell find templates -type f -iname '*.tmpl') .PHONY: lint-yaml @@ -999,7 +1000,7 @@ generate-gitignore: .PHONY: generate-images generate-images: | node_modules - npm install --no-save fabric@6.0.0-beta19 imagemin-zopfli@7 + npm install --no-save fabric@6.0.0-beta20 imagemin-zopfli@7 node tools/generate-images.js $(TAGS) .PHONY: generate-manpage diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 9853a8e05..b47e5fad0 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -441,7 +441,7 @@ INTERNAL_TOKEN = ;INTERNAL_TOKEN_URI = file:/etc/gitea/internal_token ;; ;; How long to remember that a user is logged in before requiring relogin (in days) -;LOGIN_REMEMBER_DAYS = 7 +;LOGIN_REMEMBER_DAYS = 31 ;; ;; Name of the cookie used to store the current username. ;COOKIE_USERNAME = gitea_awesome diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index 8ca1dd3b2..44c875761 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -528,7 +528,7 @@ And the following unique queues: - `INSTALL_LOCK`: **false**: Controls access to the installation page. When set to "true", the installation page is not accessible. - `SECRET_KEY`: **\**: Global secret key. This key is VERY IMPORTANT, if you lost it, the data encrypted by it (like 2FA secret) can't be decrypted anymore. - `SECRET_KEY_URI`: **_empty_**: Instead of defining SECRET_KEY, this option can be used to use the key stored in a file (example value: `file:/etc/gitea/secret_key`). It shouldn't be lost like SECRET_KEY. -- `LOGIN_REMEMBER_DAYS`: **7**: Cookie lifetime, in days. +- `LOGIN_REMEMBER_DAYS`: **31**: How long to remember that a user is logged in before requiring relogin (in days). - `COOKIE_REMEMBER_NAME`: **gitea\_incredible**: Name of cookie used to store authentication information. - `REVERSE_PROXY_AUTHENTICATION_USER`: **X-WEBAUTH-USER**: Header name for reverse proxy diff --git a/docs/content/administration/config-cheat-sheet.zh-cn.md b/docs/content/administration/config-cheat-sheet.zh-cn.md index f636927da..19b1b6c77 100644 --- a/docs/content/administration/config-cheat-sheet.zh-cn.md +++ b/docs/content/administration/config-cheat-sheet.zh-cn.md @@ -507,7 +507,7 @@ Gitea 创建以下非唯一队列: - `INSTALL_LOCK`: **false**:控制是否能够访问安装向导页面,设置为 `true` 则禁止访问安装向导页面。 - `SECRET_KEY`: **\<每次安装时随机生成\>**:全局服务器安全密钥。这个密钥非常重要,如果丢失将无法解密加密的数据(例如 2FA)。 - `SECRET_KEY_URI`: **_empty_**:与定义 `SECRET_KEY` 不同,此选项可用于使用存储在文件中的密钥(示例值:`file:/etc/gitea/secret_key`)。它不应该像 `SECRET_KEY` 一样容易丢失。 -- `LOGIN_REMEMBER_DAYS`: **7**:Cookie 保存时间,单位为天。 +- `LOGIN_REMEMBER_DAYS`: **31**:在要求重新登录之前,记住用户的登录状态多长时间(以天为单位)。 - `COOKIE_REMEMBER_NAME`: **gitea\_incredible**:保存自动登录信息的 Cookie 名称。 - `REVERSE_PROXY_AUTHENTICATION_USER`: **X-WEBAUTH-USER**:反向代理认证的 HTTP 头部名称,用于提供用户信息。 - `REVERSE_PROXY_AUTHENTICATION_EMAIL`: **X-WEBAUTH-EMAIL**:反向代理认证的 HTTP 头部名称,用于提供邮箱信息。 diff --git a/docs/content/contributing/guidelines-frontend.en-us.md b/docs/content/contributing/guidelines-frontend.en-us.md index b19101dd4..611b93e5b 100644 --- a/docs/content/contributing/guidelines-frontend.en-us.md +++ b/docs/content/contributing/guidelines-frontend.en-us.md @@ -47,7 +47,7 @@ We recommend [Google HTML/CSS Style Guide](https://google.github.io/styleguide/h 9. Avoid unnecessary `!important` in CSS, add comments to explain why it's necessary if it can't be avoided. 10. Avoid mixing different events in one event listener, prefer to use individual event listeners for every event. 11. Custom event names are recommended to use `ce-` prefix. -12. Prefer using Tailwind CSS which is available via `tw-` prefix, e.g. `tw-relative`. Gitea's helper CSS classes use `gt-` prefix (`gt-mono`), while Gitea's own private framework-level CSS classes use `g-` prefix (`g-modal-confirm`). +12. Prefer using Tailwind CSS which is available via `tw-` prefix, e.g. `tw-relative`. Gitea's helper CSS classes use `gt-` prefix (`gt-word-break`), while Gitea's own private framework-level CSS classes use `g-` prefix (`g-modal-confirm`). 13. Avoid inline scripts & styles as much as possible, it's recommended to put JS code into JS files and use CSS classes. If inline scripts & styles are unavoidable, explain the reason why it can't be avoided. ### Accessibility / ARIA @@ -118,7 +118,7 @@ However, there are still some special cases, so the current guideline is: ### Show/Hide Elements * Vue components are recommended to use `v-if` and `v-show` to show/hide elements. -* Go template code should use Gitea's `.gt-hidden` and `showElem()/hideElem()/toggleElem()`, see more details in `.gt-hidden`'s comment. +* Go template code should use `.tw-hidden` and `showElem()/hideElem()/toggleElem()`, see more details in `.tw-hidden`'s comment. ### Styles and Attributes in Go HTML Template diff --git a/docs/content/contributing/guidelines-frontend.zh-cn.md b/docs/content/contributing/guidelines-frontend.zh-cn.md index 961a331ac..1df7bc5bc 100644 --- a/docs/content/contributing/guidelines-frontend.zh-cn.md +++ b/docs/content/contributing/guidelines-frontend.zh-cn.md @@ -47,7 +47,7 @@ HTML 页面由[Go HTML Template](https://pkg.go.dev/html/template)渲染。 9. 避免在 CSS 中使用不必要的`!important`,如果无法避免,添加注释解释为什么需要它。 10. 避免在一个事件监听器中混合不同的事件,优先为每个事件使用独立的事件监听器。 11. 推荐使用自定义事件名称前缀`ce-`。 -12. 建议使用 Tailwind CSS,它可以通过 `tw-` 前缀获得,例如 `tw-relative`. Gitea 自身的助手类 CSS 使用 `gt-` 前缀(`gt-mono`),Gitea 自身的私有框架级 CSS 类使用 `g-` 前缀(`g-modal-confirm`)。 +12. 建议使用 Tailwind CSS,它可以通过 `tw-` 前缀获得,例如 `tw-relative`. Gitea 自身的助手类 CSS 使用 `gt-` 前缀(`gt-word-break`),Gitea 自身的私有框架级 CSS 类使用 `g-` 前缀(`g-modal-confirm`)。 13. 尽量避免内联脚本和样式,建议将JS代码放入JS文件中并使用CSS类。如果内联脚本和样式不可避免,请解释无法避免的原因。 ### 可访问性 / ARIA @@ -117,7 +117,7 @@ Gitea使用一些补丁使Fomantic UI更具可访问性(参见`aria.js`和`ari ### 显示/隐藏元素 * 推荐在Vue组件中使用`v-if`和`v-show`来显示/隐藏元素。 -* Go 模板代码应使用 Gitea 的 `.gt-hidden` 和 `showElem()/hideElem()/toggleElem()` 来显示/隐藏元素,请参阅`.gt-hidden`的注释以获取更多详细信息。 +* Go 模板代码应使用 `.tw-hidden` 和 `showElem()/hideElem()/toggleElem()` 来显示/隐藏元素,请参阅`.tw-hidden`的注释以获取更多详细信息。 ### Go HTML 模板中的样式和属性 diff --git a/models/db/context.go b/models/db/context.go index cda608af1..43f612518 100644 --- a/models/db/context.go +++ b/models/db/context.go @@ -120,6 +120,16 @@ func (c *halfCommitter) Close() error { // TxContext represents a transaction Context, // it will reuse the existing transaction in the parent context or create a new one. +// Some tips to use: +// +// 1 It's always recommended to use `WithTx` in new code instead of `TxContext`, since `WithTx` will handle the transaction automatically. +// 2. To maintain the old code which uses `TxContext`: +// a. Always call `Close()` before returning regardless of whether `Commit()` has been called. +// b. Always call `Commit()` before returning if there are no errors, even if the code did not change any data. +// c. Remember the `Committer` will be a halfCommitter when a transaction is being reused. +// So calling `Commit()` will do nothing, but calling `Close()` without calling `Commit()` will rollback the transaction. +// And all operations submitted by the caller stack will be rollbacked as well, not only the operations in the current function. +// d. It doesn't mean rollback is forbidden, but always do it only when there is an error, and you do want to rollback. func TxContext(parentCtx context.Context) (*Context, Committer, error) { if sess, ok := inTransaction(parentCtx); ok { return newContext(parentCtx, sess, true), &halfCommitter{committer: sess}, nil diff --git a/models/fixtures/project.yml b/models/fixtures/project.yml index 1bf8030f6..44d87bce0 100644 --- a/models/fixtures/project.yml +++ b/models/fixtures/project.yml @@ -45,3 +45,27 @@ type: 2 created_unix: 1688973000 updated_unix: 1688973000 + +- + id: 5 + title: project without default column + owner_id: 2 + repo_id: 0 + is_closed: false + creator_id: 2 + board_type: 1 + type: 2 + created_unix: 1688973000 + updated_unix: 1688973000 + +- + id: 6 + title: project with multiple default columns + owner_id: 2 + repo_id: 0 + is_closed: false + creator_id: 2 + board_type: 1 + type: 2 + created_unix: 1688973000 + updated_unix: 1688973000 diff --git a/models/fixtures/project_board.yml b/models/fixtures/project_board.yml index dc4f9cf56..3293dea6e 100644 --- a/models/fixtures/project_board.yml +++ b/models/fixtures/project_board.yml @@ -3,6 +3,7 @@ project_id: 1 title: To Do creator_id: 2 + default: true created_unix: 1588117528 updated_unix: 1588117528 @@ -29,3 +30,48 @@ creator_id: 2 created_unix: 1588117528 updated_unix: 1588117528 + +- + id: 5 + project_id: 2 + title: Backlog + creator_id: 2 + default: true + created_unix: 1588117528 + updated_unix: 1588117528 + +- + id: 6 + project_id: 4 + title: Backlog + creator_id: 2 + default: true + created_unix: 1588117528 + updated_unix: 1588117528 + +- + id: 7 + project_id: 5 + title: Done + creator_id: 2 + default: false + created_unix: 1588117528 + updated_unix: 1588117528 + +- + id: 8 + project_id: 6 + title: Backlog + creator_id: 2 + default: true + created_unix: 1588117528 + updated_unix: 1588117528 + +- + id: 9 + project_id: 6 + title: Uncategorized + creator_id: 2 + default: true + created_unix: 1588117528 + updated_unix: 1588117528 diff --git a/models/git/commit_status.go b/models/git/commit_status.go index d204d11c2..e44cc9898 100644 --- a/models/git/commit_status.go +++ b/models/git/commit_status.go @@ -25,6 +25,7 @@ import ( "code.gitea.io/gitea/modules/translation" "xorm.io/builder" + "xorm.io/xorm" ) // CommitStatus holds a single Status of a single Commit @@ -264,44 +265,48 @@ type CommitStatusIndex struct { // GetLatestCommitStatus returns all statuses with a unique context for a given commit. func GetLatestCommitStatus(ctx context.Context, repoID int64, sha string, listOptions db.ListOptions) ([]*CommitStatus, int64, error) { - ids := make([]int64, 0, 10) - sess := db.GetEngine(ctx).Table(&CommitStatus{}). - Where("repo_id = ?", repoID).And("sha = ?", sha). - Select("max( id ) as id"). - GroupBy("context_hash").OrderBy("max( id ) desc") + getBase := func() *xorm.Session { + return db.GetEngine(ctx).Table(&CommitStatus{}). + Where("repo_id = ?", repoID).And("sha = ?", sha) + } + indices := make([]int64, 0, 10) + sess := getBase().Select("max( `index` ) as `index`"). + GroupBy("context_hash").OrderBy("max( `index` ) desc") if !listOptions.IsListAll() { sess = db.SetSessionPagination(sess, &listOptions) } - count, err := sess.FindAndCount(&ids) + count, err := sess.FindAndCount(&indices) if err != nil { return nil, count, err } - statuses := make([]*CommitStatus, 0, len(ids)) - if len(ids) == 0 { + statuses := make([]*CommitStatus, 0, len(indices)) + if len(indices) == 0 { return statuses, count, nil } - return statuses, count, db.GetEngine(ctx).In("id", ids).Find(&statuses) + return statuses, count, getBase().And(builder.In("`index`", indices)).Find(&statuses) } // GetLatestCommitStatusForPairs returns all statuses with a unique context for a given list of repo-sha pairs func GetLatestCommitStatusForPairs(ctx context.Context, repoIDsToLatestCommitSHAs map[int64]string, listOptions db.ListOptions) (map[int64][]*CommitStatus, error) { type result struct { - ID int64 + Index int64 RepoID int64 } results := make([]result, 0, len(repoIDsToLatestCommitSHAs)) - sess := db.GetEngine(ctx).Table(&CommitStatus{}) + getBase := func() *xorm.Session { + return db.GetEngine(ctx).Table(&CommitStatus{}) + } // Create a disjunction of conditions for each repoID and SHA pair conds := make([]builder.Cond, 0, len(repoIDsToLatestCommitSHAs)) for repoID, sha := range repoIDsToLatestCommitSHAs { conds = append(conds, builder.Eq{"repo_id": repoID, "sha": sha}) } - sess = sess.Where(builder.Or(conds...)). - Select("max( id ) as id, repo_id"). - GroupBy("context_hash, repo_id").OrderBy("max( id ) desc") + sess := getBase().Where(builder.Or(conds...)). + Select("max( `index` ) as `index`, repo_id"). + GroupBy("context_hash, repo_id").OrderBy("max( `index` ) desc") if !listOptions.IsListAll() { sess = db.SetSessionPagination(sess, &listOptions) @@ -312,15 +317,21 @@ func GetLatestCommitStatusForPairs(ctx context.Context, repoIDsToLatestCommitSHA return nil, err } - ids := make([]int64, 0, len(results)) repoStatuses := make(map[int64][]*CommitStatus) - for _, result := range results { - ids = append(ids, result.ID) - } - statuses := make([]*CommitStatus, 0, len(ids)) - if len(ids) > 0 { - err = db.GetEngine(ctx).In("id", ids).Find(&statuses) + if len(results) > 0 { + statuses := make([]*CommitStatus, 0, len(results)) + + conds = make([]builder.Cond, 0, len(results)) + for _, result := range results { + cond := builder.Eq{ + "`index`": result.Index, + "repo_id": result.RepoID, + "sha": repoIDsToLatestCommitSHAs[result.RepoID], + } + conds = append(conds, cond) + } + err = getBase().Where(builder.Or(conds...)).Find(&statuses) if err != nil { return nil, err } @@ -337,42 +348,43 @@ func GetLatestCommitStatusForPairs(ctx context.Context, repoIDsToLatestCommitSHA // GetLatestCommitStatusForRepoCommitIDs returns all statuses with a unique context for a given list of repo-sha pairs func GetLatestCommitStatusForRepoCommitIDs(ctx context.Context, repoID int64, commitIDs []string) (map[string][]*CommitStatus, error) { type result struct { - ID int64 - Sha string + Index int64 + SHA string } + getBase := func() *xorm.Session { + return db.GetEngine(ctx).Table(&CommitStatus{}).Where("repo_id = ?", repoID) + } results := make([]result, 0, len(commitIDs)) - sess := db.GetEngine(ctx).Table(&CommitStatus{}) - - // Create a disjunction of conditions for each repoID and SHA pair conds := make([]builder.Cond, 0, len(commitIDs)) for _, sha := range commitIDs { conds = append(conds, builder.Eq{"sha": sha}) } - sess = sess.Where(builder.Eq{"repo_id": repoID}.And(builder.Or(conds...))). - Select("max( id ) as id, sha"). - GroupBy("context_hash, sha").OrderBy("max( id ) desc") + sess := getBase().And(builder.Or(conds...)). + Select("max( `index` ) as `index`, sha"). + GroupBy("context_hash, sha").OrderBy("max( `index` ) desc") err := sess.Find(&results) if err != nil { return nil, err } - ids := make([]int64, 0, len(results)) repoStatuses := make(map[string][]*CommitStatus) - for _, result := range results { - ids = append(ids, result.ID) - } - statuses := make([]*CommitStatus, 0, len(ids)) - if len(ids) > 0 { - err = db.GetEngine(ctx).In("id", ids).Find(&statuses) + if len(results) > 0 { + statuses := make([]*CommitStatus, 0, len(results)) + + conds = make([]builder.Cond, 0, len(results)) + for _, result := range results { + conds = append(conds, builder.Eq{"`index`": result.Index, "sha": result.SHA}) + } + err = getBase().And(builder.Or(conds...)).Find(&statuses) if err != nil { return nil, err } - // Group the statuses by repo ID + // Group the statuses by commit for _, status := range statuses { repoStatuses[status.SHA] = append(repoStatuses[status.SHA], status) } @@ -383,22 +395,36 @@ func GetLatestCommitStatusForRepoCommitIDs(ctx context.Context, repoID int64, co // FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts func FindRepoRecentCommitStatusContexts(ctx context.Context, repoID int64, before time.Duration) ([]string, error) { + type result struct { + Index int64 + SHA string + } + getBase := func() *xorm.Session { + return db.GetEngine(ctx).Table(&CommitStatus{}).Where("repo_id = ?", repoID) + } + start := timeutil.TimeStampNow().AddDuration(-before) - ids := make([]int64, 0, 10) - if err := db.GetEngine(ctx).Table("commit_status"). - Where("repo_id = ?", repoID). - And("updated_unix >= ?", start). - Select("max( id ) as id"). - GroupBy("context_hash").OrderBy("max( id ) desc"). - Find(&ids); err != nil { + results := make([]result, 0, 10) + + sess := getBase().And("updated_unix >= ?", start). + Select("max( `index` ) as `index`, sha"). + GroupBy("context_hash, sha").OrderBy("max( `index` ) desc") + + err := sess.Find(&results) + if err != nil { return nil, err } - contexts := make([]string, 0, len(ids)) - if len(ids) == 0 { + contexts := make([]string, 0, len(results)) + if len(results) == 0 { return contexts, nil } - return contexts, db.GetEngine(ctx).Select("context").Table("commit_status").In("id", ids).Find(&contexts) + + conds := make([]builder.Cond, 0, len(results)) + for _, result := range results { + conds = append(conds, builder.Eq{"`index`": result.Index, "sha": result.SHA}) + } + return contexts, getBase().And(builder.Or(conds...)).Select("context").Find(&contexts) } // NewCommitStatusOptions holds options for creating a CommitStatus diff --git a/models/issues/comment_code.go b/models/issues/comment_code.go index cef2ea281..735c9f089 100644 --- a/models/issues/comment_code.go +++ b/models/issues/comment_code.go @@ -117,6 +117,10 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu return nil, err } + if err := comments.LoadAttachments(ctx); err != nil { + return nil, err + } + // Find all reviews by ReviewID reviews := make(map[int64]*Review) ids := make([]int64, 0, len(comments)) diff --git a/models/issues/issue_project.go b/models/issues/issue_project.go index cc7ffb356..907a5a17b 100644 --- a/models/issues/issue_project.go +++ b/models/issues/issue_project.go @@ -49,18 +49,13 @@ func (issue *Issue) ProjectBoardID(ctx context.Context) int64 { // LoadIssuesFromBoard load issues assigned to this board func LoadIssuesFromBoard(ctx context.Context, b *project_model.Board) (IssueList, error) { - issueList := make(IssueList, 0, 10) - - if b.ID > 0 { - issues, err := Issues(ctx, &IssuesOptions{ - ProjectBoardID: b.ID, - ProjectID: b.ProjectID, - SortType: "project-column-sorting", - }) - if err != nil { - return nil, err - } - issueList = issues + issueList, err := Issues(ctx, &IssuesOptions{ + ProjectBoardID: b.ID, + ProjectID: b.ProjectID, + SortType: "project-column-sorting", + }) + if err != nil { + return nil, err } if b.Default { diff --git a/models/issues/issue_search.go b/models/issues/issue_search.go index 4e1bd9e87..921dd9973 100644 --- a/models/issues/issue_search.go +++ b/models/issues/issue_search.go @@ -21,7 +21,7 @@ import ( // IssuesOptions represents options of an issue. type IssuesOptions struct { //nolint - db.Paginator + Paginator *db.ListOptions RepoIDs []int64 // overwrites RepoCond if the length is not 0 AllPublic bool // include also all public repositories RepoCond builder.Cond @@ -104,23 +104,11 @@ func applyLimit(sess *xorm.Session, opts *IssuesOptions) *xorm.Session { return sess } - // Warning: Do not use GetSkipTake() for *db.ListOptions - // Its implementation could reset the page size with setting.API.MaxResponseItems - if listOptions, ok := opts.Paginator.(*db.ListOptions); ok { - if listOptions.Page >= 0 && listOptions.PageSize > 0 { - var start int - if listOptions.Page == 0 { - start = 0 - } else { - start = (listOptions.Page - 1) * listOptions.PageSize - } - sess.Limit(listOptions.PageSize, start) - } - return sess + start := 0 + if opts.Paginator.Page > 1 { + start = (opts.Paginator.Page - 1) * opts.Paginator.PageSize } - - start, limit := opts.Paginator.GetSkipTake() - sess.Limit(limit, start) + sess.Limit(opts.Paginator.PageSize, start) return sess } diff --git a/models/issues/issue_stats.go b/models/issues/issue_stats.go index 32c5674fc..39326616f 100644 --- a/models/issues/issue_stats.go +++ b/models/issues/issue_stats.go @@ -68,13 +68,17 @@ func CountIssuesByRepo(ctx context.Context, opts *IssuesOptions) (map[int64]int6 } // CountIssues number return of issues by given conditions. -func CountIssues(ctx context.Context, opts *IssuesOptions) (int64, error) { +func CountIssues(ctx context.Context, opts *IssuesOptions, otherConds ...builder.Cond) (int64, error) { sess := db.GetEngine(ctx). Select("COUNT(issue.id) AS count"). Table("issue"). Join("INNER", "repository", "`issue`.repo_id = `repository`.id") applyConditions(sess, opts) + for _, cond := range otherConds { + sess.And(cond) + } + return sess.Count() } diff --git a/models/issues/review.go b/models/issues/review.go index 70aba0f94..92764db4d 100644 --- a/models/issues/review.go +++ b/models/issues/review.go @@ -66,6 +66,23 @@ func (err ErrNotValidReviewRequest) Unwrap() error { return util.ErrInvalidArgument } +// ErrReviewRequestOnClosedPR represents an error when an user tries to request a re-review on a closed or merged PR. +type ErrReviewRequestOnClosedPR struct{} + +// IsErrReviewRequestOnClosedPR checks if an error is an ErrReviewRequestOnClosedPR. +func IsErrReviewRequestOnClosedPR(err error) bool { + _, ok := err.(ErrReviewRequestOnClosedPR) + return ok +} + +func (err ErrReviewRequestOnClosedPR) Error() string { + return "cannot request a re-review on a closed or merged PR" +} + +func (err ErrReviewRequestOnClosedPR) Unwrap() error { + return util.ErrPermissionDenied +} + // ReviewType defines the sort of feedback a review gives type ReviewType int @@ -618,9 +635,24 @@ func AddReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_mo return nil, err } - // skip it when reviewer hase been request to review - if review != nil && review.Type == ReviewTypeRequest { - return nil, nil + if review != nil { + // skip it when reviewer hase been request to review + if review.Type == ReviewTypeRequest { + return nil, committer.Commit() // still commit the transaction, or committer.Close() will rollback it, even if it's a reused transaction. + } + + if issue.IsClosed { + return nil, ErrReviewRequestOnClosedPR{} + } + + if issue.IsPull { + if err := issue.LoadPullRequest(ctx); err != nil { + return nil, err + } + if issue.PullRequest.HasMerged { + return nil, ErrReviewRequestOnClosedPR{} + } + } } // if the reviewer is an official reviewer, diff --git a/models/issues/review_test.go b/models/issues/review_test.go index 1868cb1bf..ac1b84ade 100644 --- a/models/issues/review_test.go +++ b/models/issues/review_test.go @@ -288,3 +288,33 @@ func TestDeleteDismissedReview(t *testing.T) { assert.NoError(t, issues_model.DeleteReview(db.DefaultContext, review)) unittest.AssertNotExistsBean(t, &issues_model.Comment{ID: comment.ID}) } + +func TestAddReviewRequest(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}) + assert.NoError(t, pull.LoadIssue(db.DefaultContext)) + issue := pull.Issue + assert.NoError(t, issue.LoadRepo(db.DefaultContext)) + reviewer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + _, err := issues_model.CreateReview(db.DefaultContext, issues_model.CreateReviewOptions{ + Issue: issue, + Reviewer: reviewer, + Type: issues_model.ReviewTypeReject, + }) + + assert.NoError(t, err) + pull.HasMerged = false + assert.NoError(t, pull.UpdateCols(db.DefaultContext, "has_merged")) + issue.IsClosed = true + _, err = issues_model.AddReviewRequest(db.DefaultContext, issue, reviewer, &user_model.User{}) + assert.Error(t, err) + assert.True(t, issues_model.IsErrReviewRequestOnClosedPR(err)) + + pull.HasMerged = true + assert.NoError(t, pull.UpdateCols(db.DefaultContext, "has_merged")) + issue.IsClosed = false + _, err = issues_model.AddReviewRequest(db.DefaultContext, issue, reviewer, &user_model.User{}) + assert.Error(t, err) + assert.True(t, issues_model.IsErrReviewRequestOnClosedPR(err)) +} diff --git a/models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project.yml b/models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project.yml new file mode 100644 index 000000000..2450d20be --- /dev/null +++ b/models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project.yml @@ -0,0 +1,23 @@ +- + id: 1 + title: project without default column + owner_id: 2 + repo_id: 0 + is_closed: false + creator_id: 2 + board_type: 1 + type: 2 + created_unix: 1688973000 + updated_unix: 1688973000 + +- + id: 2 + title: project with multiple default columns + owner_id: 2 + repo_id: 0 + is_closed: false + creator_id: 2 + board_type: 1 + type: 2 + created_unix: 1688973000 + updated_unix: 1688973000 diff --git a/models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project_board.yml b/models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project_board.yml new file mode 100644 index 000000000..2e1b1c7ee --- /dev/null +++ b/models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project_board.yml @@ -0,0 +1,26 @@ +- + id: 1 + project_id: 1 + title: Done + creator_id: 2 + default: false + created_unix: 1588117528 + updated_unix: 1588117528 + +- + id: 2 + project_id: 2 + title: Backlog + creator_id: 2 + default: true + created_unix: 1588117528 + updated_unix: 1588117528 + +- + id: 3 + project_id: 2 + title: Uncategorized + creator_id: 2 + default: true + created_unix: 1588117528 + updated_unix: 1588117528 diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 173d37234..0fa46a8d0 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -568,6 +568,12 @@ var migrations = []Migration{ NewMigration("Add default_wiki_branch to repository table", v1_22.AddDefaultWikiBranch), // v290 -> v291 NewMigration("Add PayloadVersion to HookTask", v1_22.AddPayloadVersionToHookTaskTable), + // v291 -> v292 + NewMigration("Add Index to attachment.comment_id", v1_22.AddCommentIDIndexofAttachment), + // v292 -> v293 + NewMigration("Ensure every project has exactly one default column - No Op", noopMigration), + // v293 -> v294 + NewMigration("Ensure every project has exactly one default column", v1_22.CheckProjectColumnsConsistency), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_22/v291.go b/models/migrations/v1_22/v291.go new file mode 100644 index 000000000..0bfffe5d0 --- /dev/null +++ b/models/migrations/v1_22/v291.go @@ -0,0 +1,14 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint + +import "xorm.io/xorm" + +func AddCommentIDIndexofAttachment(x *xorm.Engine) error { + type Attachment struct { + CommentID int64 `xorm:"INDEX"` + } + + return x.Sync(&Attachment{}) +} diff --git a/models/migrations/v1_22/v292.go b/models/migrations/v1_22/v292.go new file mode 100644 index 000000000..beca556ae --- /dev/null +++ b/models/migrations/v1_22/v292.go @@ -0,0 +1,9 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint + +// NOTE: noop the original migration has bug which some projects will be skip, so +// these projects will have no default board. +// So that this migration will be skipped and go to v293.go +// This file is a placeholder so that readers can know what happened diff --git a/models/migrations/v1_22/v293.go b/models/migrations/v1_22/v293.go new file mode 100644 index 000000000..53cc71929 --- /dev/null +++ b/models/migrations/v1_22/v293.go @@ -0,0 +1,108 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint + +import ( + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" +) + +// CheckProjectColumnsConsistency ensures there is exactly one default board per project present +func CheckProjectColumnsConsistency(x *xorm.Engine) error { + sess := x.NewSession() + defer sess.Close() + + limit := setting.Database.IterateBufferSize + if limit <= 0 { + limit = 50 + } + + type Project struct { + ID int64 + CreatorID int64 + BoardID int64 + } + + type ProjectBoard struct { + ID int64 `xorm:"pk autoincr"` + Title string + Default bool `xorm:"NOT NULL DEFAULT false"` // issues not assigned to a specific board will be assigned to this board + Sorting int8 `xorm:"NOT NULL DEFAULT 0"` + Color string `xorm:"VARCHAR(7)"` + + ProjectID int64 `xorm:"INDEX NOT NULL"` + CreatorID int64 `xorm:"NOT NULL"` + + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + } + + for { + if err := sess.Begin(); err != nil { + return err + } + + // all these projects without defaults will be fixed in the same loop, so + // we just need to always get projects without defaults until no such project + var projects []*Project + if err := sess.Select("project.id as id, project.creator_id, project_board.id as board_id"). + Join("LEFT", "project_board", "project_board.project_id = project.id AND project_board.`default`=?", true). + Where("project_board.id is NULL OR project_board.id = 0"). + Limit(limit). + Find(&projects); err != nil { + return err + } + + for _, p := range projects { + if _, err := sess.Insert(ProjectBoard{ + ProjectID: p.ID, + Default: true, + Title: "Uncategorized", + CreatorID: p.CreatorID, + }); err != nil { + return err + } + } + if err := sess.Commit(); err != nil { + return err + } + + if len(projects) == 0 { + break + } + } + sess.Close() + + return removeDuplicatedBoardDefault(x) +} + +func removeDuplicatedBoardDefault(x *xorm.Engine) error { + type ProjectInfo struct { + ProjectID int64 + DefaultNum int + } + var projects []ProjectInfo + if err := x.Select("project_id, count(*) AS default_num"). + Table("project_board"). + Where("`default` = ?", true). + GroupBy("project_id"). + Having("count(*) > 1"). + Find(&projects); err != nil { + return err + } + + for _, project := range projects { + if _, err := x.Where("project_id=?", project.ProjectID). + Table("project_board"). + Limit(project.DefaultNum - 1). + Update(map[string]bool{ + "`default`": false, + }); err != nil { + return err + } + } + return nil +} diff --git a/models/migrations/v1_22/v293_test.go b/models/migrations/v1_22/v293_test.go new file mode 100644 index 000000000..ccc92f39a --- /dev/null +++ b/models/migrations/v1_22/v293_test.go @@ -0,0 +1,44 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/migrations/base" + "code.gitea.io/gitea/models/project" + + "github.com/stretchr/testify/assert" +) + +func Test_CheckProjectColumnsConsistency(t *testing.T) { + // Prepare and load the testing database + x, deferable := base.PrepareTestEnv(t, 0, new(project.Project), new(project.Board)) + defer deferable() + if x == nil || t.Failed() { + return + } + + assert.NoError(t, CheckProjectColumnsConsistency(x)) + + // check if default board was added + var defaultBoard project.Board + has, err := x.Where("project_id=? AND `default` = ?", 1, true).Get(&defaultBoard) + assert.NoError(t, err) + assert.True(t, has) + assert.Equal(t, int64(1), defaultBoard.ProjectID) + assert.True(t, defaultBoard.Default) + + // check if multiple defaults, previous were removed and last will be kept + expectDefaultBoard, err := project.GetBoard(db.DefaultContext, 2) + assert.NoError(t, err) + assert.Equal(t, int64(2), expectDefaultBoard.ProjectID) + assert.False(t, expectDefaultBoard.Default) + + expectNonDefaultBoard, err := project.GetBoard(db.DefaultContext, 3) + assert.NoError(t, err) + assert.Equal(t, int64(2), expectNonDefaultBoard.ProjectID) + assert.True(t, expectNonDefaultBoard.Default) +} diff --git a/models/project/board.go b/models/project/board.go index c0e652988..5f142a356 100644 --- a/models/project/board.go +++ b/models/project/board.go @@ -123,6 +123,17 @@ func createBoardsForProjectsType(ctx context.Context, project *Project) error { return nil } + board := Board{ + CreatedUnix: timeutil.TimeStampNow(), + CreatorID: project.CreatorID, + Title: "Backlog", + ProjectID: project.ID, + Default: true, + } + if err := db.Insert(ctx, board); err != nil { + return err + } + if len(items) == 0 { return nil } @@ -176,6 +187,10 @@ func deleteBoardByID(ctx context.Context, boardID int64) error { return err } + if board.Default { + return fmt.Errorf("deleteBoardByID: cannot delete default board") + } + if err = board.removeIssues(ctx); err != nil { return err } @@ -194,7 +209,6 @@ func deleteBoardByProjectID(ctx context.Context, projectID int64) error { // GetBoard fetches the current board of a project func GetBoard(ctx context.Context, boardID int64) (*Board, error) { board := new(Board) - has, err := db.GetEngine(ctx).ID(boardID).Get(board) if err != nil { return nil, err @@ -228,7 +242,6 @@ func UpdateBoard(ctx context.Context, board *Board) error { } // GetBoards fetches all boards related to a project -// if no default board set, first board is a temporary "Uncategorized" board func (p *Project) GetBoards(ctx context.Context) (BoardList, error) { boards := make([]*Board, 0, 5) @@ -244,53 +257,64 @@ func (p *Project) GetBoards(ctx context.Context) (BoardList, error) { return append([]*Board{defaultB}, boards...), nil } -// getDefaultBoard return default board and create a dummy if none exist +// getDefaultBoard return default board and ensure only one exists func (p *Project) getDefaultBoard(ctx context.Context) (*Board, error) { var board Board - exist, err := db.GetEngine(ctx).Where("project_id=? AND `default`=?", p.ID, true).Get(&board) + has, err := db.GetEngine(ctx). + Where("project_id=? AND `default` = ?", p.ID, true). + Desc("id").Get(&board) if err != nil { return nil, err } - if exist { + + if has { return &board, nil } - // represents a board for issues not assigned to one - return &Board{ + // create a default board if none is found + board = Board{ ProjectID: p.ID, - Title: "Uncategorized", Default: true, - }, nil + Title: "Uncategorized", + CreatorID: p.CreatorID, + } + if _, err := db.GetEngine(ctx).Insert(&board); err != nil { + return nil, err + } + return &board, nil } // SetDefaultBoard represents a board for issues not assigned to one -// if boardID is 0 unset default func SetDefaultBoard(ctx context.Context, projectID, boardID int64) error { - _, err := db.GetEngine(ctx).Where(builder.Eq{ - "project_id": projectID, - "`default`": true, - }).Cols("`default`").Update(&Board{Default: false}) - if err != nil { - return err - } + return db.WithTx(ctx, func(ctx context.Context) error { + if _, err := GetBoard(ctx, boardID); err != nil { + return err + } - if boardID > 0 { - _, err = db.GetEngine(ctx).ID(boardID).Where(builder.Eq{"project_id": projectID}). + if _, err := db.GetEngine(ctx).Where(builder.Eq{ + "project_id": projectID, + "`default`": true, + }).Cols("`default`").Update(&Board{Default: false}); err != nil { + return err + } + + _, err := db.GetEngine(ctx).ID(boardID). + Where(builder.Eq{"project_id": projectID}). Cols("`default`").Update(&Board{Default: true}) - } - - return err + return err + }) } // UpdateBoardSorting update project board sorting func UpdateBoardSorting(ctx context.Context, bs BoardList) error { - for i := range bs { - _, err := db.GetEngine(ctx).ID(bs[i].ID).Cols( - "sorting", - ).Update(bs[i]) - if err != nil { - return err + return db.WithTx(ctx, func(ctx context.Context) error { + for i := range bs { + if _, err := db.GetEngine(ctx).ID(bs[i].ID).Cols( + "sorting", + ).Update(bs[i]); err != nil { + return err + } } - } - return nil + return nil + }) } diff --git a/models/project/board_test.go b/models/project/board_test.go new file mode 100644 index 000000000..71ba29a58 --- /dev/null +++ b/models/project/board_test.go @@ -0,0 +1,44 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package project + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/unittest" + + "github.com/stretchr/testify/assert" +) + +func TestGetDefaultBoard(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + projectWithoutDefault, err := GetProjectByID(db.DefaultContext, 5) + assert.NoError(t, err) + + // check if default board was added + board, err := projectWithoutDefault.getDefaultBoard(db.DefaultContext) + assert.NoError(t, err) + assert.Equal(t, int64(5), board.ProjectID) + assert.Equal(t, "Uncategorized", board.Title) + + projectWithMultipleDefaults, err := GetProjectByID(db.DefaultContext, 6) + assert.NoError(t, err) + + // check if multiple defaults were removed + board, err = projectWithMultipleDefaults.getDefaultBoard(db.DefaultContext) + assert.NoError(t, err) + assert.Equal(t, int64(6), board.ProjectID) + assert.Equal(t, int64(9), board.ID) + + // set 8 as default board + assert.NoError(t, SetDefaultBoard(db.DefaultContext, board.ProjectID, 8)) + + // then 9 will become a non-default board + board, err = GetBoard(db.DefaultContext, 9) + assert.NoError(t, err) + assert.Equal(t, int64(6), board.ProjectID) + assert.False(t, board.Default) +} diff --git a/models/project/project_test.go b/models/project/project_test.go index 7a37c1faf..8fbbdedec 100644 --- a/models/project/project_test.go +++ b/models/project/project_test.go @@ -92,19 +92,19 @@ func TestProjectsSort(t *testing.T) { }{ { sortType: "default", - wants: []int64{1, 3, 2, 4}, + wants: []int64{1, 3, 2, 6, 5, 4}, }, { sortType: "oldest", - wants: []int64{4, 2, 3, 1}, + wants: []int64{4, 5, 6, 2, 3, 1}, }, { sortType: "recentupdate", - wants: []int64{1, 3, 2, 4}, + wants: []int64{1, 3, 2, 6, 5, 4}, }, { sortType: "leastupdate", - wants: []int64{4, 2, 3, 1}, + wants: []int64{4, 5, 6, 2, 3, 1}, }, } @@ -113,8 +113,8 @@ func TestProjectsSort(t *testing.T) { OrderBy: GetSearchOrderByBySortType(tt.sortType), }) assert.NoError(t, err) - assert.EqualValues(t, int64(4), count) - if assert.Len(t, projects, 4) { + assert.EqualValues(t, int64(6), count) + if assert.Len(t, projects, 6) { for i := range projects { assert.EqualValues(t, tt.wants[i], projects[i].ID) } diff --git a/modules/indexer/internal/paginator.go b/modules/indexer/internal/paginator.go index de0a33c06..ee204bf04 100644 --- a/modules/indexer/internal/paginator.go +++ b/modules/indexer/internal/paginator.go @@ -10,7 +10,7 @@ import ( ) // ParsePaginator parses a db.Paginator into a skip and limit -func ParsePaginator(paginator db.Paginator, max ...int) (int, int) { +func ParsePaginator(paginator *db.ListOptions, max ...int) (int, int) { // Use a very large number to indicate no limit unlimited := math.MaxInt32 if len(max) > 0 { @@ -19,22 +19,15 @@ func ParsePaginator(paginator db.Paginator, max ...int) (int, int) { } if paginator == nil || paginator.IsListAll() { + // It shouldn't happen. In actual usage scenarios, there should not be requests to search all. + // But if it does happen, respect it and return "unlimited". + // And it's also useful for testing. return 0, unlimited } - // Warning: Do not use GetSkipTake() for *db.ListOptions - // Its implementation could reset the page size with setting.API.MaxResponseItems - if listOptions, ok := paginator.(*db.ListOptions); ok { - if listOptions.Page >= 0 && listOptions.PageSize > 0 { - var start int - if listOptions.Page == 0 { - start = 0 - } else { - start = (listOptions.Page - 1) * listOptions.PageSize - } - return start, listOptions.PageSize - } - return 0, unlimited + if paginator.PageSize == 0 { + // Do not return any results when searching, it's used to get the total count only. + return 0, 0 } return paginator.GetSkipTake() diff --git a/modules/indexer/issues/db/db.go b/modules/indexer/issues/db/db.go index 1016523b7..05ec54843 100644 --- a/modules/indexer/issues/db/db.go +++ b/modules/indexer/issues/db/db.go @@ -78,6 +78,17 @@ func (i *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( return nil, err } + // If pagesize == 0, return total count only. It's a special case for search count. + if options.Paginator != nil && options.Paginator.PageSize == 0 { + total, err := issue_model.CountIssues(ctx, opt, cond) + if err != nil { + return nil, err + } + return &internal.SearchResult{ + Total: total, + }, nil + } + ids, total, err := issue_model.IssueIDs(ctx, opt, cond) if err != nil { return nil, err diff --git a/modules/indexer/issues/indexer.go b/modules/indexer/issues/indexer.go index e3bc21b49..1cb86feb8 100644 --- a/modules/indexer/issues/indexer.go +++ b/modules/indexer/issues/indexer.go @@ -308,7 +308,7 @@ func SearchIssues(ctx context.Context, opts *SearchOptions) ([]int64, int64, err // CountIssues counts issues by options. It is a shortcut of SearchIssues(ctx, opts) but only returns the total count. func CountIssues(ctx context.Context, opts *SearchOptions) (int64, error) { - opts = opts.Copy(func(options *SearchOptions) { opts.Paginator = &db_model.ListOptions{PageSize: 0} }) + opts = opts.Copy(func(options *SearchOptions) { options.Paginator = &db_model.ListOptions{PageSize: 0} }) _, total, err := SearchIssues(ctx, opts) return total, err diff --git a/modules/indexer/issues/internal/model.go b/modules/indexer/issues/internal/model.go index b7102c35a..e9c4eca55 100644 --- a/modules/indexer/issues/internal/model.go +++ b/modules/indexer/issues/internal/model.go @@ -106,7 +106,7 @@ type SearchOptions struct { UpdatedAfterUnix optional.Option[int64] UpdatedBeforeUnix optional.Option[int64] - db.Paginator + Paginator *db.ListOptions SortBy SortBy // sort by field } diff --git a/modules/indexer/issues/internal/tests/tests.go b/modules/indexer/issues/internal/tests/tests.go index 2209377c2..7f32876d8 100644 --- a/modules/indexer/issues/internal/tests/tests.go +++ b/modules/indexer/issues/internal/tests/tests.go @@ -77,6 +77,13 @@ func TestIndexer(t *testing.T, indexer internal.Indexer) { assert.Equal(t, c.ExpectedIDs, ids) assert.Equal(t, c.ExpectedTotal, result.Total) } + + // test counting + c.SearchOptions.Paginator = &db.ListOptions{PageSize: 0} + countResult, err := indexer.Search(context.Background(), c.SearchOptions) + require.NoError(t, err) + assert.Empty(t, countResult.Hits) + assert.Equal(t, result.Total, countResult.Total) }) } } diff --git a/modules/indexer/issues/meilisearch/meilisearch.go b/modules/indexer/issues/meilisearch/meilisearch.go index b735c2696..8a7cec6cb 100644 --- a/modules/indexer/issues/meilisearch/meilisearch.go +++ b/modules/indexer/issues/meilisearch/meilisearch.go @@ -218,6 +218,14 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( skip, limit := indexer_internal.ParsePaginator(options.Paginator, maxTotalHits) + counting := limit == 0 + if counting { + // If set limit to 0, it will be 20 by default, and -1 is not allowed. + // See https://www.meilisearch.com/docs/reference/api/search#limit + // So set limit to 1 to make the cost as low as possible, then clear the result before returning. + limit = 1 + } + keyword := options.Keyword if !options.IsFuzzyKeyword { // to make it non fuzzy ("typo tolerance" in meilisearch terms), we have to quote the keyword(s) @@ -236,6 +244,10 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( return nil, err } + if counting { + searchRes.Hits = nil + } + hits, err := convertHits(searchRes) if err != nil { return nil, err diff --git a/modules/setting/security.go b/modules/setting/security.go index 380360a69..3d7b1f9ce 100644 --- a/modules/setting/security.go +++ b/modules/setting/security.go @@ -103,7 +103,7 @@ func generateSaveInternalToken(rootCfg ConfigProvider) { func loadSecurityFrom(rootCfg ConfigProvider) { sec := rootCfg.Section("security") InstallLock = HasInstallLock(rootCfg) - LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(7) + LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(31) SecretKey = loadSecret(sec, "SECRET_KEY_URI", "SECRET_KEY") if SecretKey == "" { // FIXME: https://github.com/go-gitea/gitea/issues/16832 diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go index cdff31698..5deb051ab 100644 --- a/modules/templates/util_render.go +++ b/modules/templates/util_render.go @@ -40,7 +40,7 @@ func RenderCommitMessage(ctx context.Context, msg string, metas map[string]strin if len(msgLines) == 0 { return template.HTML("") } - return template.HTML(msgLines[0]) + return RenderCodeBlock(template.HTML(msgLines[0])) } // RenderCommitMessageLinkSubject renders commit message as a XSS-safe link to @@ -67,7 +67,7 @@ func RenderCommitMessageLinkSubject(ctx context.Context, msg, urlDefault string, log.Error("RenderCommitMessageSubject: %v", err) return template.HTML("") } - return template.HTML(renderedMessage) + return RenderCodeBlock(template.HTML(renderedMessage)) } // RenderCommitBody extracts the body of a commit message without its title. diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index c83f386f2..c28ed6c37 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1423,7 +1423,6 @@ projects.type.basic_kanban = Basic Kanban projects.type.bug_triage = Bug Triage projects.template.desc = Template projects.template.desc_helper = Select a project template to get started -projects.type.uncategorized = Uncategorized projects.column.edit = Edit Column projects.column.edit_title = Name projects.column.new_title = Name @@ -1431,10 +1430,8 @@ projects.column.new_submit = Create Column projects.column.new = New Column projects.column.set_default = Set Default projects.column.set_default_desc = Set this column as default for uncategorized issues and pulls -projects.column.unset_default = Unset Default -projects.column.unset_default_desc = Unset this column as default projects.column.delete = Delete Column -projects.column.deletion_desc = Deleting a project column moves all related issues to "Uncategorized". Continue? +projects.column.deletion_desc = Deleting a project column moves all related issues to the default column. Continue? projects.column.color = Color projects.open = Open projects.close = Close diff --git a/package-lock.json b/package-lock.json index f6df16f2a..535842850 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,10 +11,10 @@ "@citation-js/plugin-software-formats": "0.6.1", "@claviska/jquery-minicolors": "2.3.6", "@github/markdown-toolbar-element": "2.2.3", - "@github/relative-time-element": "4.3.1", + "@github/relative-time-element": "4.4.0", "@github/text-expander-element": "2.6.1", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", - "@primer/octicons": "19.8.0", + "@primer/octicons": "19.9.0", "add-asset-webpack-plugin": "2.0.1", "ansi_up": "6.0.2", "asciinema-player": "3.7.1", @@ -40,7 +40,7 @@ "monaco-editor": "0.47.0", "monaco-editor-webpack-plugin": "7.1.0", "pdfobject": "2.3.0", - "postcss": "8.4.35", + "postcss": "8.4.38", "postcss-loader": "8.1.1", "postcss-nesting": "12.1.0", "pretty-ms": "9.0.0", @@ -59,7 +59,7 @@ "vue-chartjs": "5.3.0", "vue-loader": "17.4.2", "vue3-calendar-heatmap": "2.0.5", - "webpack": "5.90.3", + "webpack": "5.91.0", "webpack-cli": "5.1.4", "wrap-ansi": "9.0.0" }, @@ -77,24 +77,24 @@ "eslint-plugin-jquery": "1.5.1", "eslint-plugin-no-jquery": "2.7.0", "eslint-plugin-no-use-extend-native": "0.5.0", - "eslint-plugin-regexp": "2.3.0", + "eslint-plugin-regexp": "2.4.0", "eslint-plugin-sonarjs": "0.24.0", "eslint-plugin-unicorn": "51.0.1", - "eslint-plugin-vitest": "0.3.26", - "eslint-plugin-vitest-globals": "1.4.0", - "eslint-plugin-vue": "9.23.0", - "eslint-plugin-vue-scoped-css": "2.7.2", + "eslint-plugin-vitest": "0.4.0", + "eslint-plugin-vitest-globals": "1.5.0", + "eslint-plugin-vue": "9.24.0", + "eslint-plugin-vue-scoped-css": "2.8.0", "eslint-plugin-wc": "2.0.4", - "happy-dom": "14.2.0", + "happy-dom": "14.3.7", "markdownlint-cli": "0.39.0", "postcss-html": "1.6.0", - "stylelint": "16.2.1", + "stylelint": "16.3.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-strict-value": "1.10.4", "svgo": "3.2.0", - "updates": "15.3.1", + "updates": "16.0.0", "vite-string-plugin": "1.1.5", - "vitest": "1.3.1" + "vitest": "1.4.0" }, "engines": { "node": ">= 18.0.0" @@ -516,6 +516,16 @@ "node": ">=10.0.0" } }, + "node_modules/@dual-bundle/import-meta-resolve": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz", + "integrity": "sha512-ZKXyJeFAzcpKM2kk8ipoGIPUqx9BX52omTGnfwjJvxOCaZTM2wtDK7zN0aIgPRbT9XYAlha0HtmZ+XKteuh0Gw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", @@ -994,9 +1004,9 @@ "integrity": "sha512-AlquKGee+IWiAMYVB0xyHFZRMnu4n3X4HTvJHu79GiVJ1ojTukCWyxMlF5NMsecoLcBKsuBhx3QPv2vkE/zQ0A==" }, "node_modules/@github/relative-time-element": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.3.1.tgz", - "integrity": "sha512-zL79nlhZVCg7x2Pf/HT5MB0mowmErE71VXpF10/3Wy8dQwkninNO1M9aOizh2wKC5LkSpDXqNYjDZwbH0/bcSg==" + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.4.0.tgz", + "integrity": "sha512-CrI6oAecoahG7PF5dsgjdvlF5kCtusVMjg810EULD81TvnDsP+k/FRi/ClFubWLgBo4EGpr2EfvmumtqQFo7ow==" }, "node_modules/@github/text-expander-element": { "version": "2.6.1", @@ -1365,9 +1375,9 @@ } }, "node_modules/@primer/octicons": { - "version": "19.8.0", - "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-19.8.0.tgz", - "integrity": "sha512-Imze/fyW41Io5fN+27T5EAeXJrgBjMbz6nzU+wYbRylXvIAjLPUvaJPVoStiFlgSU+TjTUJqg5A9rgMDzTyMCg==", + "version": "19.9.0", + "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-19.9.0.tgz", + "integrity": "sha512-uAZa9cMgWkzbEsZnYWB7tg0vt7QprubD7ljtprz2fBJ8CjyqoxFRRsFvH4UiJdjK/3o87ODgDkhiflyJXDh+Lg==", "dependencies": { "object-assign": "^4.1.1" } @@ -2238,16 +2248,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.3.1.tgz", - "integrity": "sha512-STEDMVQGww5lhCuNXVSQfbfuNII5E08QWkvAw5Qwf+bj2WT+JkG1uc+5/vXA3AOYMDHVOSpL+9rcbEUiHIm2dw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.4.0.tgz", + "integrity": "sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.3.1", - "@typescript-eslint/type-utils": "7.3.1", - "@typescript-eslint/utils": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1", + "@typescript-eslint/scope-manager": "7.4.0", + "@typescript-eslint/type-utils": "7.4.0", + "@typescript-eslint/utils": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -2273,15 +2283,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.3.1.tgz", - "integrity": "sha512-Rq49+pq7viTRCH48XAbTA+wdLRrB/3sRq4Lpk0oGDm0VmnjBrAOVXH/Laalmwsv2VpekiEfVFwJYVk6/e8uvQw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.4.0.tgz", + "integrity": "sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.3.1", - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/typescript-estree": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1", + "@typescript-eslint/scope-manager": "7.4.0", + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/typescript-estree": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0", "debug": "^4.3.4" }, "engines": { @@ -2301,13 +2311,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.3.1.tgz", - "integrity": "sha512-fVS6fPxldsKY2nFvyT7IP78UO1/I2huG+AYu5AMjCT9wtl6JFiDnsv4uad4jQ0GTFzcUV5HShVeN96/17bTBag==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.4.0.tgz", + "integrity": "sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1" + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2318,13 +2328,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.3.1.tgz", - "integrity": "sha512-iFhaysxFsMDQlzJn+vr3OrxN8NmdQkHks4WaqD4QBnt5hsq234wcYdyQ9uquzJJIDAj5W4wQne3yEsYA6OmXGw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.4.0.tgz", + "integrity": "sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.3.1", - "@typescript-eslint/utils": "7.3.1", + "@typescript-eslint/typescript-estree": "7.4.0", + "@typescript-eslint/utils": "7.4.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -2345,9 +2355,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.3.1.tgz", - "integrity": "sha512-2tUf3uWggBDl4S4183nivWQ2HqceOZh1U4hhu4p1tPiIJoRRXrab7Y+Y0p+dozYwZVvLPRI6r5wKe9kToF9FIw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.4.0.tgz", + "integrity": "sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2358,13 +2368,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.3.1.tgz", - "integrity": "sha512-tLpuqM46LVkduWP7JO7yVoWshpJuJzxDOPYIVWUUZbW+4dBpgGeUdl/fQkhuV0A8eGnphYw3pp8d2EnvPOfxmQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.4.0.tgz", + "integrity": "sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1", + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2386,17 +2396,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.3.1.tgz", - "integrity": "sha512-jIERm/6bYQ9HkynYlNZvXpzmXWZGhMbrOvq3jJzOSOlKXsVjrrolzWBjDW6/TvT5Q3WqaN4EkmcfdQwi9tDjBQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.4.0.tgz", + "integrity": "sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.3.1", - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/typescript-estree": "7.3.1", + "@typescript-eslint/scope-manager": "7.4.0", + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/typescript-estree": "7.4.0", "semver": "^7.5.4" }, "engines": { @@ -2411,12 +2421,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.3.1.tgz", - "integrity": "sha512-9RMXwQF8knsZvfv9tdi+4D/j7dMG28X/wMJ8Jj6eOHyHWwDW4ngQJcqEczSsqIKKjFiLFr40Mnr7a5ulDD3vmw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.4.0.tgz", + "integrity": "sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.3.1", + "@typescript-eslint/types": "7.4.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -2447,13 +2457,13 @@ } }, "node_modules/@vitest/expect": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.3.1.tgz", - "integrity": "sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.4.0.tgz", + "integrity": "sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==", "dev": true, "dependencies": { - "@vitest/spy": "1.3.1", - "@vitest/utils": "1.3.1", + "@vitest/spy": "1.4.0", + "@vitest/utils": "1.4.0", "chai": "^4.3.10" }, "funding": { @@ -2461,12 +2471,12 @@ } }, "node_modules/@vitest/runner": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.3.1.tgz", - "integrity": "sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.4.0.tgz", + "integrity": "sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==", "dev": true, "dependencies": { - "@vitest/utils": "1.3.1", + "@vitest/utils": "1.4.0", "p-limit": "^5.0.0", "pathe": "^1.1.1" }, @@ -2502,9 +2512,9 @@ } }, "node_modules/@vitest/snapshot": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.3.1.tgz", - "integrity": "sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.4.0.tgz", + "integrity": "sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==", "dev": true, "dependencies": { "magic-string": "^0.30.5", @@ -2528,9 +2538,9 @@ } }, "node_modules/@vitest/spy": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.3.1.tgz", - "integrity": "sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.4.0.tgz", + "integrity": "sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==", "dev": true, "dependencies": { "tinyspy": "^2.2.0" @@ -2540,9 +2550,9 @@ } }, "node_modules/@vitest/utils": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.3.1.tgz", - "integrity": "sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.4.0.tgz", + "integrity": "sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==", "dev": true, "dependencies": { "diff-sequences": "^29.6.3", @@ -3475,9 +3485,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001599", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz", - "integrity": "sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==", + "version": "1.0.30001600", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz", + "integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==", "funding": [ { "type": "opencollective", @@ -4721,9 +4731,9 @@ } }, "node_modules/dompurify": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.10.tgz", - "integrity": "sha512-WZDL8ZHTliEVP3Lk4phtvjg8SNQ3YMc5WVstxE8cszKZrFjzI4PF4ZTIk9VGAc9vZADO7uGO2V/ZiStcRSAT4Q==" + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.11.tgz", + "integrity": "sha512-Fan4uMuyB26gFV3ovPoEoQbxRRPfTu3CvImyZnhGq5fsIEO+gEFLp45ISFt+kQBWsK5ulDdT0oV28jS1UrwQLg==" }, "node_modules/domutils": { "version": "3.1.0", @@ -4766,9 +4776,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.713", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.713.tgz", - "integrity": "sha512-vDarADhwntXiULEdmWd77g2dV6FrNGa8ecAC29MZ4TwPut2fvosD0/5sJd1qWNNe8HcJFAC+F5Lf9jW1NPtWmw==" + "version": "1.4.716", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.716.tgz", + "integrity": "sha512-t/MXMzFKQC3UfMDpw7V5wdB/UAB8dWx4hEsy+fpPYJWW3gqh3u5T1uXp6vR+H6dGCPBxkRo+YBcapBLvbGQHRw==" }, "node_modules/elkjs": { "version": "0.9.2", @@ -4899,19 +4909,19 @@ } }, "node_modules/es-aggregate-error": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/es-aggregate-error/-/es-aggregate-error-1.0.12.tgz", - "integrity": "sha512-j0PupcmELoVbYS2NNrsn5zcLLEsryQwP02x8fRawh7c2eEaPHwJFAxltZsqV7HJjsF57+SMpYyVRWgbVLfOagg==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/es-aggregate-error/-/es-aggregate-error-1.0.13.tgz", + "integrity": "sha512-KkzhUUuD2CUMqEc8JEqsXEMDHzDPE8RCjZeUBitsnB1eNcAJWQPiciKsMXe3Yytj4Flw1XLl46Qcf9OxvZha7A==", "dev": true, "dependencies": { - "define-data-property": "^1.1.1", + "define-data-property": "^1.1.4", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.1.0", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "globalthis": "^1.0.3", - "has-property-descriptors": "^1.0.1", - "set-function-name": "^2.0.1" + "has-property-descriptors": "^1.0.2", + "set-function-name": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -4967,9 +4977,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.2.tgz", - "integrity": "sha512-7nOqkomXZEaxUDJw21XZNtRk739QvrPSoZoRtbsEfcii00vdzZUh6zh1CQwHhrib8MdEtJfv5rJiGeb4KuV/vw==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.0.tgz", + "integrity": "sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw==" }, "node_modules/es-object-atoms": { "version": "1.0.0", @@ -5164,9 +5174,9 @@ } }, "node_modules/eslint-compat-utils": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.4.1.tgz", - "integrity": "sha512-5N7ZaJG5pZxUeNNJfUchurLVrunD1xJvyg5kYOIVF8kg1f3ajTikmAu/5fZ9w100omNPOoMjngRszh/Q/uFGMg==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.0.tgz", + "integrity": "sha512-dc6Y8tzEcSYZMHa+CMPLi/hyo1FzNeonbhJL7Ol0ccuKQkwopJcJBA9YL/xmMTLU1eKigXo9vj9nALElWYSowg==", "dev": true, "dependencies": { "semver": "^7.5.4" @@ -5598,9 +5608,9 @@ } }, "node_modules/eslint-plugin-regexp": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.3.0.tgz", - "integrity": "sha512-T8JUs7ssRGbuXb+CGfdUJbcxTBMCNOpNqNBLuC8JUKAEIez72J37RaOi5/4dAUsGz92GbWVtqTLPSJZGyP/sQA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.4.0.tgz", + "integrity": "sha512-OL2S6VPjQhs9s/NclQ0qattVq1J0GU8ox70/HIVy5Dxw+qbbdd7KQkyucsez2clEQjvdtDe12DTnPphFFUyXFg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", @@ -5664,12 +5674,12 @@ } }, "node_modules/eslint-plugin-vitest": { - "version": "0.3.26", - "resolved": "https://registry.npmjs.org/eslint-plugin-vitest/-/eslint-plugin-vitest-0.3.26.tgz", - "integrity": "sha512-oxe5JSPgRjco8caVLTh7Ti8PxpwJdhSV0hTQAmkFcNcmy/9DnqLB/oNVRA11RmVRP//2+jIIT6JuBEcpW3obYg==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vitest/-/eslint-plugin-vitest-0.4.0.tgz", + "integrity": "sha512-3oWgZIwdWVBQ5plvkmOBjreIGLQRdYb7x54OP8uIRHeZyRVJIdOn9o/qWVb9292fDMC8jn7H7d9TSFBZqhrykQ==", "dev": true, "dependencies": { - "@typescript-eslint/utils": "^7.1.1" + "@typescript-eslint/utils": "^7.2.0" }, "engines": { "node": "^18.0.0 || >= 20.0.0" @@ -5688,18 +5698,19 @@ } }, "node_modules/eslint-plugin-vitest-globals": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-vitest-globals/-/eslint-plugin-vitest-globals-1.4.0.tgz", - "integrity": "sha512-WE+YlK9X9s4vf5EaYRU0Scw7WItDZStm+PapFSYlg2ABNtaQ4zIG7wEqpoUB3SlfM+SgkhgmzR0TeJOO5k3/Nw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vitest-globals/-/eslint-plugin-vitest-globals-1.5.0.tgz", + "integrity": "sha512-ZSsVOaOIig0oVLzRTyk8lUfBfqzWxr/J3/NFMfGGRIkGQPejJYmDH3gXmSJxAojts77uzAGB/UmVrwi2DC4LYA==", "dev": true }, "node_modules/eslint-plugin-vue": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.23.0.tgz", - "integrity": "sha512-Bqd/b7hGYGrlV+wP/g77tjyFmp81lh5TMw0be9093X02SyelxRRfCI6/IsGq/J7Um0YwB9s0Ry0wlFyjPdmtUw==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.24.0.tgz", + "integrity": "sha512-9SkJMvF8NGMT9aQCwFc5rj8Wo1XWSMSHk36i7ZwdI614BU7sIOR28ZjuFPKp8YGymZN12BSEbiSwa7qikp+PBw==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", + "globals": "^13.24.0", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", "postcss-selector-parser": "^6.0.15", @@ -5715,13 +5726,13 @@ } }, "node_modules/eslint-plugin-vue-scoped-css": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue-scoped-css/-/eslint-plugin-vue-scoped-css-2.7.2.tgz", - "integrity": "sha512-myJ99CJuwmAx5kq1WjgIeaUkxeU6PIEUh7age79Alm30bhN4fVTapOQLSMlvVTgxr36Y3igsZ3BCJM32LbHHig==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue-scoped-css/-/eslint-plugin-vue-scoped-css-2.8.0.tgz", + "integrity": "sha512-JXb3Um4+AhuDGxSX6FAGCI0p811xF7W8L7yxC8wmAEZEI/teTjlpC09noqQZHXn53RZ/TGQJ8Onaq4teYLxBbg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "eslint-compat-utils": "^0.4.0", + "eslint-compat-utils": "^0.5.0", "lodash": "^4.17.21", "postcss": "^8.4.31", "postcss-safe-parser": "^6.0.0", @@ -6502,9 +6513,9 @@ } }, "node_modules/happy-dom": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-14.2.0.tgz", - "integrity": "sha512-vTqF/9MEkRKgYy5eKq9W0uiNmkgnVAmJhRwn8x8fQBR7lc4C84859jLhgZ1lR4Gi/t70oSdgvtLpxlHjgdJrAw==", + "version": "14.3.7", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-14.3.7.tgz", + "integrity": "sha512-lUfDRGzjrVJF2pnvh13OL+qEJ9eDpcedVLm77a3aMg8gPGKXfG+xFMNk3cOWetjucU8FveJ4qcSC/EX55nJ4fQ==", "dev": true, "dependencies": { "entities": "^4.5.0", @@ -7584,9 +7595,9 @@ } }, "node_modules/known-css-properties": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.29.0.tgz", - "integrity": "sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==", + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.30.0.tgz", + "integrity": "sha512-VSWXYUnsPu9+WYKkfmJyLKtIvaRJi1kXUqVmBACORXZQxT5oZDsoZ2vQP+bQFDnWtpI/4eq3MLoRMjI2fnLzTQ==", "dev": true }, "node_modules/language-subtag-registry": { @@ -9346,9 +9357,9 @@ } }, "node_modules/postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "funding": [ { "type": "opencollective", @@ -9366,7 +9377,7 @@ "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -10676,14 +10687,17 @@ } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", - "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -10776,15 +10790,16 @@ "dev": true }, "node_modules/stylelint": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.2.1.tgz", - "integrity": "sha512-SfIMGFK+4n7XVAyv50CpVfcGYWG4v41y6xG7PqOgQSY8M/PgdK0SQbjWFblxjJZlN9jNq879mB4BCZHJRIJ1hA==", + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.3.0.tgz", + "integrity": "sha512-hqC6xNTbQ5HRGQXfIW4HwXcx09raIFz4W4XFbraeqWqYRVVY/ibYvI0dsu0ORMQY8re2bpDdCAeIa2cm+QJ4Sw==", "dev": true, "dependencies": { - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3", - "@csstools/media-query-list-parser": "^2.1.7", - "@csstools/selector-specificity": "^3.0.1", + "@csstools/css-parser-algorithms": "^2.6.1", + "@csstools/css-tokenizer": "^2.2.4", + "@csstools/media-query-list-parser": "^2.1.9", + "@csstools/selector-specificity": "^3.0.2", + "@dual-bundle/import-meta-resolve": "^4.0.0", "balanced-match": "^2.0.0", "colord": "^2.9.3", "cosmiconfig": "^9.0.0", @@ -10798,19 +10813,19 @@ "globby": "^11.1.0", "globjoin": "^0.1.4", "html-tags": "^3.3.1", - "ignore": "^5.3.0", + "ignore": "^5.3.1", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", - "known-css-properties": "^0.29.0", + "known-css-properties": "^0.30.0", "mathml-tag-names": "^2.1.3", - "meow": "^13.1.0", + "meow": "^13.2.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "picocolors": "^1.0.0", - "postcss": "^8.4.33", + "postcss": "^8.4.38", "postcss-resolve-nested-selector": "^0.1.1", "postcss-safe-parser": "^7.0.0", - "postcss-selector-parser": "^6.0.15", + "postcss-selector-parser": "^6.0.16", "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", "string-width": "^4.2.3", @@ -11418,9 +11433,9 @@ "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" }, "node_modules/tinypool": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.2.tgz", - "integrity": "sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==", + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.3.tgz", + "integrity": "sha512-Ud7uepAklqRH1bvwy22ynrliC7Dljz7Tm8M/0RBUW+YRa4YHhZ6e4PpgE+fu1zr/WqB1kbeuVrdfeuyIBpy4tw==", "dev": true, "engines": { "node": ">=14.0.0" @@ -11611,9 +11626,9 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz", - "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", "dev": true, "dependencies": { "call-bind": "^1.0.7", @@ -11737,12 +11752,12 @@ } }, "node_modules/updates": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/updates/-/updates-15.3.1.tgz", - "integrity": "sha512-DqHT1aJ6p6jVLWRiAeuVx/TQotvEwUjgrY1Mlc0a2qYk+eKEQVXugQ4M+6QoVMA3X1NFAVsb02d93pmWam4bBA==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/updates/-/updates-16.0.0.tgz", + "integrity": "sha512-Ra3QUu/rfbSCsG83zNNvoRQt0FVT3qULBSALYTlwTDX6oyz7R5GQAYwqJoIG/RDjYAXpwr3usrInoyHHTP6cag==", "dev": true, "bin": { - "updates": "bin/updates.js" + "updates": "dist/updates.js" }, "engines": { "node": ">=18" @@ -11825,9 +11840,9 @@ } }, "node_modules/vite": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.2.tgz", - "integrity": "sha512-FWZbz0oSdLq5snUI0b6sULbz58iXFXdvkZfZWR/F0ZJuKTSPO7v72QPXt6KqYeMFb0yytNp6kZosxJ96Nr/wDQ==", + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.6.tgz", + "integrity": "sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==", "dev": true, "dependencies": { "esbuild": "^0.20.1", @@ -11880,9 +11895,9 @@ } }, "node_modules/vite-node": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.3.1.tgz", - "integrity": "sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.4.0.tgz", + "integrity": "sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -11927,34 +11942,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/vite/node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, "node_modules/vite/node_modules/rollup": { "version": "4.13.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz", @@ -11988,16 +11975,16 @@ } }, "node_modules/vitest": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.3.1.tgz", - "integrity": "sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.4.0.tgz", + "integrity": "sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==", "dev": true, "dependencies": { - "@vitest/expect": "1.3.1", - "@vitest/runner": "1.3.1", - "@vitest/snapshot": "1.3.1", - "@vitest/spy": "1.3.1", - "@vitest/utils": "1.3.1", + "@vitest/expect": "1.4.0", + "@vitest/runner": "1.4.0", + "@vitest/snapshot": "1.4.0", + "@vitest/spy": "1.4.0", + "@vitest/utils": "1.4.0", "acorn-walk": "^8.3.2", "chai": "^4.3.10", "debug": "^4.3.4", @@ -12011,7 +11998,7 @@ "tinybench": "^2.5.1", "tinypool": "^0.8.2", "vite": "^5.0.0", - "vite-node": "1.3.1", + "vite-node": "1.4.0", "why-is-node-running": "^2.2.2" }, "bin": { @@ -12026,8 +12013,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.3.1", - "@vitest/ui": "1.3.1", + "@vitest/browser": "1.4.0", + "@vitest/ui": "1.4.0", "happy-dom": "*", "jsdom": "*" }, @@ -12186,25 +12173,25 @@ } }, "node_modules/webpack": { - "version": "5.90.3", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz", - "integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==", + "version": "5.91.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", + "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", "acorn-import-assertions": "^1.9.0", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", + "enhanced-resolve": "^5.16.0", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", + "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", @@ -12212,7 +12199,7 @@ "schema-utils": "^3.2.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.3.10", - "watchpack": "^2.4.0", + "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { diff --git a/package.json b/package.json index 77a58dacf..b5bfda9dc 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,10 @@ "@citation-js/plugin-software-formats": "0.6.1", "@claviska/jquery-minicolors": "2.3.6", "@github/markdown-toolbar-element": "2.2.3", - "@github/relative-time-element": "4.3.1", + "@github/relative-time-element": "4.4.0", "@github/text-expander-element": "2.6.1", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", - "@primer/octicons": "19.8.0", + "@primer/octicons": "19.9.0", "add-asset-webpack-plugin": "2.0.1", "ansi_up": "6.0.2", "asciinema-player": "3.7.1", @@ -39,7 +39,7 @@ "monaco-editor": "0.47.0", "monaco-editor-webpack-plugin": "7.1.0", "pdfobject": "2.3.0", - "postcss": "8.4.35", + "postcss": "8.4.38", "postcss-loader": "8.1.1", "postcss-nesting": "12.1.0", "pretty-ms": "9.0.0", @@ -58,7 +58,7 @@ "vue-chartjs": "5.3.0", "vue-loader": "17.4.2", "vue3-calendar-heatmap": "2.0.5", - "webpack": "5.90.3", + "webpack": "5.91.0", "webpack-cli": "5.1.4", "wrap-ansi": "9.0.0" }, @@ -76,24 +76,24 @@ "eslint-plugin-jquery": "1.5.1", "eslint-plugin-no-jquery": "2.7.0", "eslint-plugin-no-use-extend-native": "0.5.0", - "eslint-plugin-regexp": "2.3.0", + "eslint-plugin-regexp": "2.4.0", "eslint-plugin-sonarjs": "0.24.0", "eslint-plugin-unicorn": "51.0.1", - "eslint-plugin-vitest": "0.3.26", - "eslint-plugin-vitest-globals": "1.4.0", - "eslint-plugin-vue": "9.23.0", - "eslint-plugin-vue-scoped-css": "2.7.2", + "eslint-plugin-vitest": "0.4.0", + "eslint-plugin-vitest-globals": "1.5.0", + "eslint-plugin-vue": "9.24.0", + "eslint-plugin-vue-scoped-css": "2.8.0", "eslint-plugin-wc": "2.0.4", - "happy-dom": "14.2.0", + "happy-dom": "14.3.7", "markdownlint-cli": "0.39.0", "postcss-html": "1.6.0", - "stylelint": "16.2.1", + "stylelint": "16.3.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-strict-value": "1.10.4", "svgo": "3.2.0", - "updates": "15.3.1", + "updates": "16.0.0", "vite-string-plugin": "1.1.5", - "vitest": "1.3.1" + "vitest": "1.4.0" }, "browserslist": [ "defaults" diff --git a/poetry.lock b/poetry.lock index 46520fba3..951a0fa7a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -113,18 +113,15 @@ six = ">=1.13.0" [[package]] name = "json5" -version = "0.9.18" +version = "0.9.24" description = "A Python implementation of the JSON5 data format." optional = false python-versions = ">=3.8" files = [ - {file = "json5-0.9.18-py2.py3-none-any.whl", hash = "sha256:3f20193ff8dfdec6ab114b344e7ac5d76fac453c8bab9bdfe1460d1d528ec393"}, - {file = "json5-0.9.18.tar.gz", hash = "sha256:ecb8ac357004e3522fb989da1bf08b146011edbd14fdffae6caad3bd68493467"}, + {file = "json5-0.9.24-py3-none-any.whl", hash = "sha256:4ca101fd5c7cb47960c055ef8f4d0e31e15a7c6c48c3b6f1473fc83b6c462a13"}, + {file = "json5-0.9.24.tar.gz", hash = "sha256:0c638399421da959a20952782800e5c1a78c14e08e1dc9738fa10d8ec14d58c8"}, ] -[package.extras] -dev = ["hypothesis"] - [[package]] name = "pathspec" version = "0.12.1" diff --git a/routers/api/packages/generic/generic.go b/routers/api/packages/generic/generic.go index b65870a8d..823293113 100644 --- a/routers/api/packages/generic/generic.go +++ b/routers/api/packages/generic/generic.go @@ -8,6 +8,7 @@ import ( "net/http" "regexp" "strings" + "unicode" packages_model "code.gitea.io/gitea/models/packages" "code.gitea.io/gitea/modules/log" @@ -18,8 +19,8 @@ import ( ) var ( - packageNameRegex = regexp.MustCompile(`\A[A-Za-z0-9\.\_\-\+]+\z`) - filenameRegex = packageNameRegex + packageNameRegex = regexp.MustCompile(`\A[-_+.\w]+\z`) + filenameRegex = regexp.MustCompile(`\A[-_+=:;.()\[\]{}~!@#$%^& \w]+\z`) ) func apiError(ctx *context.Context, status int, obj any) { @@ -54,20 +55,38 @@ func DownloadPackageFile(ctx *context.Context) { helper.ServePackageFile(ctx, s, u, pf) } +func isValidPackageName(packageName string) bool { + if len(packageName) == 1 && !unicode.IsLetter(rune(packageName[0])) && !unicode.IsNumber(rune(packageName[0])) { + return false + } + return packageNameRegex.MatchString(packageName) && packageName != ".." +} + +func isValidFileName(filename string) bool { + return filenameRegex.MatchString(filename) && + strings.TrimSpace(filename) == filename && + filename != "." && filename != ".." +} + // UploadPackage uploads the specific generic package. // Duplicated packages get rejected. func UploadPackage(ctx *context.Context) { packageName := ctx.Params("packagename") filename := ctx.Params("filename") - if !packageNameRegex.MatchString(packageName) || !filenameRegex.MatchString(filename) { - apiError(ctx, http.StatusBadRequest, errors.New("Invalid package name or filename")) + if !isValidPackageName(packageName) { + apiError(ctx, http.StatusBadRequest, errors.New("invalid package name")) + return + } + + if !isValidFileName(filename) { + apiError(ctx, http.StatusBadRequest, errors.New("invalid filename")) return } packageVersion := ctx.Params("packageversion") if packageVersion != strings.TrimSpace(packageVersion) { - apiError(ctx, http.StatusBadRequest, errors.New("Invalid package version")) + apiError(ctx, http.StatusBadRequest, errors.New("invalid package version")) return } diff --git a/routers/api/packages/generic/generic_test.go b/routers/api/packages/generic/generic_test.go new file mode 100644 index 000000000..1acaafe57 --- /dev/null +++ b/routers/api/packages/generic/generic_test.go @@ -0,0 +1,65 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package generic + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestValidatePackageName(t *testing.T) { + bad := []string{ + "", + ".", + "..", + "-", + "a?b", + "a b", + "a/b", + } + for _, name := range bad { + assert.False(t, isValidPackageName(name), "bad=%q", name) + } + + good := []string{ + "a", + "1", + "a-", + "a_b", + "c.d+", + } + for _, name := range good { + assert.True(t, isValidPackageName(name), "good=%q", name) + } +} + +func TestValidateFileName(t *testing.T) { + bad := []string{ + "", + ".", + "..", + "a?b", + "a/b", + " a", + "a ", + } + for _, name := range bad { + assert.False(t, isValidFileName(name), "bad=%q", name) + } + + good := []string{ + "-", + "a", + "1", + "a-", + "a_b", + "a b", + "c.d+", + `-_+=:;.()[]{}~!@#$%^& aA1`, + } + for _, name := range good { + assert.True(t, isValidFileName(name), "good=%q", name) + } +} diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index 72ada25ec..712cba745 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -150,7 +150,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) { return } - // OK, now the blob is known to have at most 1024 bytes we can simply read this in in one go (This saves reading it twice) + // OK, now the blob is known to have at most 1024 bytes we can simply read this in one go (This saves reading it twice) dataRc, err := blob.DataAsync() if err != nil { ctx.ServerError("DataAsync", err) diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go index 9ccbb57c5..77c0d25e2 100644 --- a/routers/api/v1/repo/pull_review.go +++ b/routers/api/v1/repo/pull_review.go @@ -787,6 +787,8 @@ func DeleteReviewRequests(ctx *context.APIContext) { // "$ref": "#/responses/empty" // "422": // "$ref": "#/responses/validationError" + // "403": + // "$ref": "#/responses/forbidden" // "404": // "$ref": "#/responses/notFound" opts := web.GetForm(ctx).(*api.PullReviewRequestOptions) @@ -855,6 +857,10 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions for _, reviewer := range reviewers { comment, err := issue_service.ReviewRequest(ctx, pr.Issue, ctx.Doer, reviewer, isAdd) if err != nil { + if issues_model.IsErrReviewRequestOnClosedPR(err) { + ctx.Error(http.StatusForbidden, "", err) + return + } ctx.Error(http.StatusInternalServerError, "ReviewRequest", err) return } @@ -1068,7 +1074,7 @@ func dismissReview(ctx *context.APIContext, msg string, isDismiss, dismissPriors ctx.Error(http.StatusForbidden, "", "Must be repo admin") return } - review, pr, isWrong := prepareSingleReview(ctx) + review, _, isWrong := prepareSingleReview(ctx) if isWrong { return } @@ -1078,13 +1084,12 @@ func dismissReview(ctx *context.APIContext, msg string, isDismiss, dismissPriors return } - if pr.Issue.IsClosed { - ctx.Error(http.StatusForbidden, "", "not need to dismiss this review because this pr is closed") - return - } - _, err := pull_service.DismissReview(ctx, review.ID, ctx.Repo.Repository.ID, msg, ctx.Doer, isDismiss, dismissPriors) if err != nil { + if pull_service.IsErrDismissRequestOnClosedPR(err) { + ctx.Error(http.StatusForbidden, "", err) + return + } ctx.Error(http.StatusInternalServerError, "pull_service.DismissReview", err) return } diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go index ad8bb90d9..db1dac012 100644 --- a/routers/web/org/projects.go +++ b/routers/web/org/projects.go @@ -207,11 +207,7 @@ func ChangeProjectStatus(ctx *context.Context) { id := ctx.ParamsInt64(":id") if err := project_model.ChangeProjectStatusByRepoIDAndID(ctx, 0, id, toClose); err != nil { - if project_model.IsErrProjectNotExist(err) { - ctx.NotFound("", err) - } else { - ctx.ServerError("ChangeProjectStatusByRepoIDAndID", err) - } + ctx.NotFoundOrServerError("ChangeProjectStatusByRepoIDAndID", project_model.IsErrProjectNotExist, err) return } ctx.Redirect(ctx.ContextUser.HomeLink() + "/-/projects?state=" + url.QueryEscape(ctx.Params(":action"))) @@ -221,11 +217,7 @@ func ChangeProjectStatus(ctx *context.Context) { func DeleteProject(ctx *context.Context) { p, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id")) if err != nil { - if project_model.IsErrProjectNotExist(err) { - ctx.NotFound("", nil) - } else { - ctx.ServerError("GetProjectByID", err) - } + ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err) return } if p.OwnerID != ctx.ContextUser.ID { @@ -254,11 +246,7 @@ func RenderEditProject(ctx *context.Context) { p, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id")) if err != nil { - if project_model.IsErrProjectNotExist(err) { - ctx.NotFound("", nil) - } else { - ctx.ServerError("GetProjectByID", err) - } + ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err) return } if p.OwnerID != ctx.ContextUser.ID { @@ -303,11 +291,7 @@ func EditProjectPost(ctx *context.Context) { p, err := project_model.GetProjectByID(ctx, projectID) if err != nil { - if project_model.IsErrProjectNotExist(err) { - ctx.NotFound("", nil) - } else { - ctx.ServerError("GetProjectByID", err) - } + ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err) return } if p.OwnerID != ctx.ContextUser.ID { @@ -335,11 +319,7 @@ func EditProjectPost(ctx *context.Context) { func ViewProject(ctx *context.Context) { project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id")) if err != nil { - if project_model.IsErrProjectNotExist(err) { - ctx.NotFound("", nil) - } else { - ctx.ServerError("GetProjectByID", err) - } + ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err) return } if project.OwnerID != ctx.ContextUser.ID { @@ -353,10 +333,6 @@ func ViewProject(ctx *context.Context) { return } - if boards[0].ID == 0 { - boards[0].Title = ctx.Locale.TrString("repo.projects.type.uncategorized") - } - issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards) if err != nil { ctx.ServerError("LoadIssuesOfBoards", err) @@ -493,11 +469,7 @@ func DeleteProjectBoard(ctx *context.Context) { project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id")) if err != nil { - if project_model.IsErrProjectNotExist(err) { - ctx.NotFound("", nil) - } else { - ctx.ServerError("GetProjectByID", err) - } + ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err) return } @@ -534,11 +506,7 @@ func AddBoardToProjectPost(ctx *context.Context) { project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id")) if err != nil { - if project_model.IsErrProjectNotExist(err) { - ctx.NotFound("", nil) - } else { - ctx.ServerError("GetProjectByID", err) - } + ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err) return } @@ -566,11 +534,7 @@ func CheckProjectBoardChangePermissions(ctx *context.Context) (*project_model.Pr project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id")) if err != nil { - if project_model.IsErrProjectNotExist(err) { - ctx.NotFound("", nil) - } else { - ctx.ServerError("GetProjectByID", err) - } + ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err) return nil, nil } @@ -636,21 +600,6 @@ func SetDefaultProjectBoard(ctx *context.Context) { ctx.JSONOK() } -// UnsetDefaultProjectBoard unset default board for uncategorized issues/pulls -func UnsetDefaultProjectBoard(ctx *context.Context) { - project, _ := CheckProjectBoardChangePermissions(ctx) - if ctx.Written() { - return - } - - if err := project_model.SetDefaultBoard(ctx, project.ID, 0); err != nil { - ctx.ServerError("SetDefaultBoard", err) - return - } - - ctx.JSONOK() -} - // MoveIssues moves or keeps issues in a column and sorts them inside that column func MoveIssues(ctx *context.Context) { if ctx.Doer == nil { @@ -662,11 +611,7 @@ func MoveIssues(ctx *context.Context) { project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id")) if err != nil { - if project_model.IsErrProjectNotExist(err) { - ctx.NotFound("ProjectNotExist", nil) - } else { - ctx.ServerError("GetProjectByID", err) - } + ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err) return } if project.OwnerID != ctx.ContextUser.ID { @@ -674,28 +619,15 @@ func MoveIssues(ctx *context.Context) { return } - var board *project_model.Board + board, err := project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID")) + if err != nil { + ctx.NotFoundOrServerError("GetProjectBoard", project_model.IsErrProjectBoardNotExist, err) + return + } - if ctx.ParamsInt64(":boardID") == 0 { - board = &project_model.Board{ - ID: 0, - ProjectID: project.ID, - Title: ctx.Locale.TrString("repo.projects.type.uncategorized"), - } - } else { - board, err = project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID")) - if err != nil { - if project_model.IsErrProjectBoardNotExist(err) { - ctx.NotFound("ProjectBoardNotExist", nil) - } else { - ctx.ServerError("GetProjectBoard", err) - } - return - } - if board.ProjectID != project.ID { - ctx.NotFound("BoardNotInProject", nil) - return - } + if board.ProjectID != project.ID { + ctx.NotFound("BoardNotInProject", nil) + return } type movedIssuesForm struct { @@ -718,11 +650,7 @@ func MoveIssues(ctx *context.Context) { } movedIssues, err := issues_model.GetIssuesByIDs(ctx, issueIDs) if err != nil { - if issues_model.IsErrIssueNotExist(err) { - ctx.NotFound("IssueNotExisting", nil) - } else { - ctx.ServerError("GetIssueByID", err) - } + ctx.NotFoundOrServerError("GetIssueByID", issues_model.IsErrIssueNotExist, err) return } diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go index 809a5009b..eea3d4dc0 100644 --- a/routers/web/repo/blame.go +++ b/routers/web/repo/blame.go @@ -260,7 +260,7 @@ func renderBlame(ctx *context.Context, blameParts []*git.BlamePart, commitNames if commit.User != nil { avatar = string(avatarUtils.Avatar(commit.User, 18)) } else { - avatar = string(avatarUtils.AvatarByEmail(commit.Author.Email, commit.Author.Name, 18, "gt-mr-3")) + avatar = string(avatarUtils.AvatarByEmail(commit.Author.Email, commit.Author.Name, 18, "tw-mr-2")) } br.Avatar = gotemplate.HTML(avatar) diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index c0ec12133..a4e3f7165 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -1600,7 +1600,7 @@ func ViewIssue(ctx *context.Context) { } marked[issue.PosterID] = issue.ShowRole - // Render comments and and fetch participants. + // Render comments and fetch participants. participants[0] = issue.Poster for _, comment = range issue.Comments { comment.Issue = issue @@ -2494,6 +2494,10 @@ func UpdatePullReviewRequest(ctx *context.Context) { _, err = issue_service.ReviewRequest(ctx, issue, ctx.Doer, reviewer, action == "attach") if err != nil { + if issues_model.IsErrReviewRequestOnClosedPR(err) { + ctx.Status(http.StatusForbidden) + return + } ctx.ServerError("ReviewRequest", err) return } diff --git a/routers/web/repo/issue_content_history.go b/routers/web/repo/issue_content_history.go index dfee2863b..c817d6aa9 100644 --- a/routers/web/repo/issue_content_history.go +++ b/routers/web/repo/issue_content_history.go @@ -70,7 +70,7 @@ func GetContentHistoryList(ctx *context.Context) { } src := html.EscapeString(item.UserAvatarLink) - class := avatars.DefaultAvatarClass + " gt-mr-3" + class := avatars.DefaultAvatarClass + " tw-mr-2" name := html.EscapeString(username) avatarHTML := string(templates.AvatarHTML(src, 28, class, username)) timeSinceText := string(timeutil.TimeSinceUnix(item.EditedUnix, ctx.Locale)) diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go index 4c171defb..aa4258559 100644 --- a/routers/web/repo/projects.go +++ b/routers/web/repo/projects.go @@ -314,10 +314,6 @@ func ViewProject(ctx *context.Context) { return } - if boards[0].ID == 0 { - boards[0].Title = ctx.Locale.TrString("repo.projects.type.uncategorized") - } - issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards) if err != nil { ctx.ServerError("LoadIssuesOfBoards", err) @@ -582,21 +578,6 @@ func SetDefaultProjectBoard(ctx *context.Context) { ctx.JSONOK() } -// UnSetDefaultProjectBoard unset default board for uncategorized issues/pulls -func UnSetDefaultProjectBoard(ctx *context.Context) { - project, _ := checkProjectBoardChangePermissions(ctx) - if ctx.Written() { - return - } - - if err := project_model.SetDefaultBoard(ctx, project.ID, 0); err != nil { - ctx.ServerError("SetDefaultBoard", err) - return - } - - ctx.JSONOK() -} - // MoveIssues moves or keeps issues in a column and sorts them inside that column func MoveIssues(ctx *context.Context) { if ctx.Doer == nil { @@ -627,28 +608,19 @@ func MoveIssues(ctx *context.Context) { return } - var board *project_model.Board + board, err := project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID")) + if err != nil { + if project_model.IsErrProjectBoardNotExist(err) { + ctx.NotFound("ProjectBoardNotExist", nil) + } else { + ctx.ServerError("GetProjectBoard", err) + } + return + } - if ctx.ParamsInt64(":boardID") == 0 { - board = &project_model.Board{ - ID: 0, - ProjectID: project.ID, - Title: ctx.Locale.TrString("repo.projects.type.uncategorized"), - } - } else { - board, err = project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID")) - if err != nil { - if project_model.IsErrProjectBoardNotExist(err) { - ctx.NotFound("ProjectBoardNotExist", nil) - } else { - ctx.ServerError("GetProjectBoard", err) - } - return - } - if board.ProjectID != project.ID { - ctx.NotFound("BoardNotInProject", nil) - return - } + if board.ProjectID != project.ID { + ctx.NotFound("BoardNotInProject", nil) + return } type movedIssuesForm struct { diff --git a/routers/web/repo/pull_review.go b/routers/web/repo/pull_review.go index ead7592fd..afa3b17d3 100644 --- a/routers/web/repo/pull_review.go +++ b/routers/web/repo/pull_review.go @@ -263,6 +263,10 @@ func DismissReview(ctx *context.Context) { form := web.GetForm(ctx).(*forms.DismissReviewForm) comm, err := pull_service.DismissReview(ctx, form.ReviewID, ctx.Repo.Repository.ID, form.Message, ctx.Doer, true, true) if err != nil { + if pull_service.IsErrDismissRequestOnClosedPR(err) { + ctx.Status(http.StatusForbidden) + return + } ctx.ServerError("pull_service.DismissReview", err) return } diff --git a/routers/web/repo/render.go b/routers/web/repo/render.go index 10fa21c60..e64db03e2 100644 --- a/routers/web/repo/render.go +++ b/routers/web/repo/render.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/typesniffer" "code.gitea.io/gitea/modules/util" @@ -44,20 +45,17 @@ func RenderFile(ctx *context.Context) { isTextFile := st.IsText() rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{}) + ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'; sandbox allow-scripts") if markupType := markup.Type(blob.Name()); markupType == "" { if isTextFile { - _, err = io.Copy(ctx.Resp, rd) - if err != nil { - ctx.ServerError("Copy", err) - } - return + _, _ = io.Copy(ctx.Resp, rd) + } else { + http.Error(ctx.Resp, "Unsupported file type render", http.StatusInternalServerError) } - ctx.Error(http.StatusInternalServerError, "Unsupported file type render") return } - ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'; sandbox allow-scripts") err = markup.Render(&markup.RenderContext{ Ctx: ctx, RelativePath: ctx.Repo.TreePath, @@ -71,7 +69,8 @@ func RenderFile(ctx *context.Context) { InStandalonePage: true, }, rd, ctx.Resp) if err != nil { - ctx.ServerError("Render", err) + log.Error("Failed to render file %q: %v", ctx.Repo.TreePath, err) + http.Error(ctx.Resp, "Failed to render file", http.StatusInternalServerError) return } } diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index becb8748c..a65a10021 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -938,9 +938,9 @@ func prepareOpenWithEditorApps(ctx *context.Context) { schema, _, _ := strings.Cut(app.OpenURL, ":") var iconHTML template.HTML if schema == "vscode" || schema == "vscodium" || schema == "jetbrains" { - iconHTML = svg.RenderHTML(fmt.Sprintf("gitea-open-with-%s", schema), 16, "gt-mr-3") + iconHTML = svg.RenderHTML(fmt.Sprintf("gitea-open-with-%s", schema), 16, "tw-mr-2") } else { - iconHTML = svg.RenderHTML("gitea-git", 16, "gt-mr-3") // TODO: it could support user's customized icon in the future + iconHTML = svg.RenderHTML("gitea-git", 16, "tw-mr-2") // TODO: it could support user's customized icon in the future } tmplApps = append(tmplApps, map[string]any{ "DisplayName": app.DisplayName, diff --git a/routers/web/web.go b/routers/web/web.go index 7329acd15..40f4ffc01 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -986,7 +986,6 @@ func registerRoutes(m *web.Route) { m.Put("", web.Bind(forms.EditProjectBoardForm{}), org.EditProjectBoard) m.Delete("", org.DeleteProjectBoard) m.Post("/default", org.SetDefaultProjectBoard) - m.Post("/unsetdefault", org.UnsetDefaultProjectBoard) m.Post("/move", org.MoveIssues) }) @@ -1360,7 +1359,6 @@ func registerRoutes(m *web.Route) { m.Put("", web.Bind(forms.EditProjectBoardForm{}), repo.EditProjectBoard) m.Delete("", repo.DeleteProjectBoard) m.Post("/default", repo.SetDefaultProjectBoard) - m.Post("/unsetdefault", repo.UnSetDefaultProjectBoard) m.Post("/move", repo.MoveIssues) }) diff --git a/services/auth/source/oauth2/providers.go b/services/auth/source/oauth2/providers.go index c3edae4ab..6ed6c184e 100644 --- a/services/auth/source/oauth2/providers.go +++ b/services/auth/source/oauth2/providers.go @@ -59,7 +59,7 @@ func (p *AuthSourceProvider) DisplayName() string { func (p *AuthSourceProvider) IconHTML(size int) template.HTML { if p.iconURL != "" { - img := fmt.Sprintf(`%s`, + img := fmt.Sprintf(`%s`, size, size, html.EscapeString(p.iconURL), html.EscapeString(p.DisplayName()), diff --git a/services/auth/source/oauth2/providers_base.go b/services/auth/source/oauth2/providers_base.go index 5b6694487..9d4ab106e 100644 --- a/services/auth/source/oauth2/providers_base.go +++ b/services/auth/source/oauth2/providers_base.go @@ -35,10 +35,10 @@ func (b *BaseProvider) IconHTML(size int) template.HTML { case "github": svgName = "octicon-mark-github" } - svgHTML := svg.RenderHTML(svgName, size, "gt-mr-3") + svgHTML := svg.RenderHTML(svgName, size, "tw-mr-2") if svgHTML == "" { log.Error("No SVG icon for oauth2 provider %q", b.name) - svgHTML = svg.RenderHTML("gitea-openid", size, "gt-mr-3") + svgHTML = svg.RenderHTML("gitea-openid", size, "tw-mr-2") } return svgHTML } diff --git a/services/auth/source/oauth2/providers_openid.go b/services/auth/source/oauth2/providers_openid.go index a4dcfcafc..285876d5a 100644 --- a/services/auth/source/oauth2/providers_openid.go +++ b/services/auth/source/oauth2/providers_openid.go @@ -29,7 +29,7 @@ func (o *OpenIDProvider) DisplayName() string { // IconHTML returns icon HTML for this provider func (o *OpenIDProvider) IconHTML(size int) template.HTML { - return svg.RenderHTML("gitea-openid", size, "gt-mr-3") + return svg.RenderHTML("gitea-openid", size, "tw-mr-2") } // CreateGothProvider creates a GothProvider from this Provider diff --git a/services/doctor/fix16961.go b/services/doctor/fix16961.go index d3f36d8d5..50d9ac662 100644 --- a/services/doctor/fix16961.go +++ b/services/doctor/fix16961.go @@ -216,6 +216,12 @@ func fixBrokenRepoUnit16961(repoUnit *repo_model.RepoUnit, bs []byte) (fixed boo return false, nil } + var cfg any + err = json.UnmarshalHandleDoubleEncode(bs, &cfg) + if err == nil { + return false, nil + } + switch repoUnit.Type { case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypeProjects: cfg := &repo_model.UnitConfig{} diff --git a/services/doctor/storage.go b/services/doctor/storage.go index f33853786..787df2754 100644 --- a/services/doctor/storage.go +++ b/services/doctor/storage.go @@ -162,7 +162,7 @@ func checkStorage(opts *checkStorageOptions) func(ctx context.Context, logger lo if opts.RepoArchives || opts.All { if err := commonCheckStorage(ctx, logger, autofix, &commonStorageCheckOptions{ - storer: storage.RepoAvatars, + storer: storage.RepoArchives, isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) { exists, err := repo.ExistsRepoArchiverWithStoragePath(ctx, path) if err == nil || errors.Is(err, util.ErrInvalidArgument) { diff --git a/services/migrations/migrate.go b/services/migrations/migrate.go index ae164a7ad..367818c0e 100644 --- a/services/migrations/migrate.go +++ b/services/migrations/migrate.go @@ -257,14 +257,13 @@ func migrateRepository(ctx context.Context, doer *user_model.User, downloader ba } log.Warn("migrating milestones is not supported, ignored") } - msBatchSize := uploader.MaxBatchInsertSize("milestone") for len(milestones) > 0 { if len(milestones) < msBatchSize { msBatchSize = len(milestones) } - if err := uploader.CreateMilestones(milestones...); err != nil { + if err := uploader.CreateMilestones(milestones[:msBatchSize]...); err != nil { return err } milestones = milestones[msBatchSize:] diff --git a/services/pull/review.go b/services/pull/review.go index 7d698a14f..6ad931b67 100644 --- a/services/pull/review.go +++ b/services/pull/review.go @@ -20,11 +20,29 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" notify_service "code.gitea.io/gitea/services/notify" ) var notEnoughLines = regexp.MustCompile(`fatal: file .* has only \d+ lines?`) +// ErrDismissRequestOnClosedPR represents an error when an user tries to dismiss a review associated to a closed or merged PR. +type ErrDismissRequestOnClosedPR struct{} + +// IsErrDismissRequestOnClosedPR checks if an error is an ErrDismissRequestOnClosedPR. +func IsErrDismissRequestOnClosedPR(err error) bool { + _, ok := err.(ErrDismissRequestOnClosedPR) + return ok +} + +func (err ErrDismissRequestOnClosedPR) Error() string { + return "can't dismiss a review associated to a closed or merged PR" +} + +func (err ErrDismissRequestOnClosedPR) Unwrap() error { + return util.ErrPermissionDenied +} + // checkInvalidation checks if the line of code comment got changed by another commit. // If the line got changed the comment is going to be invalidated. func checkInvalidation(ctx context.Context, c *issues_model.Comment, doer *user_model.User, repo *git.Repository, branch string) error { @@ -382,6 +400,21 @@ func DismissReview(ctx context.Context, reviewID, repoID int64, message string, return nil, fmt.Errorf("reviews's repository is not the same as the one we expect") } + issue := review.Issue + + if issue.IsClosed { + return nil, ErrDismissRequestOnClosedPR{} + } + + if issue.IsPull { + if err := issue.LoadPullRequest(ctx); err != nil { + return nil, err + } + if issue.PullRequest.HasMerged { + return nil, ErrDismissRequestOnClosedPR{} + } + } + if err := issues_model.DismissReview(ctx, review, isDismiss); err != nil { return nil, err } diff --git a/services/pull/review_test.go b/services/pull/review_test.go new file mode 100644 index 000000000..3bce1e523 --- /dev/null +++ b/services/pull/review_test.go @@ -0,0 +1,48 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package pull_test + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + pull_service "code.gitea.io/gitea/services/pull" + + "github.com/stretchr/testify/assert" +) + +func TestDismissReview(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{}) + assert.NoError(t, pull.LoadIssue(db.DefaultContext)) + issue := pull.Issue + assert.NoError(t, issue.LoadRepo(db.DefaultContext)) + reviewer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + review, err := issues_model.CreateReview(db.DefaultContext, issues_model.CreateReviewOptions{ + Issue: issue, + Reviewer: reviewer, + Type: issues_model.ReviewTypeReject, + }) + + assert.NoError(t, err) + issue.IsClosed = true + pull.HasMerged = false + assert.NoError(t, issues_model.UpdateIssueCols(db.DefaultContext, issue, "is_closed")) + assert.NoError(t, pull.UpdateCols(db.DefaultContext, "has_merged")) + _, err = pull_service.DismissReview(db.DefaultContext, review.ID, issue.RepoID, "", &user_model.User{}, false, false) + assert.Error(t, err) + assert.True(t, pull_service.IsErrDismissRequestOnClosedPR(err)) + + pull.HasMerged = true + pull.Issue.IsClosed = false + assert.NoError(t, issues_model.UpdateIssueCols(db.DefaultContext, issue, "is_closed")) + assert.NoError(t, pull.UpdateCols(db.DefaultContext, "has_merged")) + _, err = pull_service.DismissReview(db.DefaultContext, review.ID, issue.RepoID, "", &user_model.User{}, false, false) + assert.Error(t, err) + assert.True(t, pull_service.IsErrDismissRequestOnClosedPR(err)) +} diff --git a/services/webhook/deliver_test.go b/services/webhook/deliver_test.go index bc06e43e0..c68933266 100644 --- a/services/webhook/deliver_test.go +++ b/services/webhook/deliver_test.go @@ -113,7 +113,6 @@ func TestWebhookDeliverAuthorizationHeader(t *testing.T) { err := hook.SetHeaderAuthorization("Bearer s3cr3t-t0ken") assert.NoError(t, err) assert.NoError(t, webhook_model.CreateWebhook(db.DefaultContext, hook)) - db.GetEngine(db.DefaultContext).NoAutoTime().DB().Logger.ShowSQL(true) hookTask := &webhook_model.HookTask{ HookID: hook.ID, diff --git a/tailwind.config.js b/tailwind.config.js index 0754ab363..d49e9d7a1 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -41,6 +41,8 @@ export default { // classes that don't work without CSS variables from "@tailwind base" which we don't use 'transform', 'shadow', 'ring', 'blur', 'grayscale', 'invert', '!invert', 'filter', '!filter', 'backdrop-filter', + // we use double-class tw-hidden defined in web_src/css/helpers.css for increased specificity + 'hidden', // unneeded classes '[-a-zA-Z:0-9_.]', ], @@ -66,6 +68,10 @@ export default { '3xl': '24px', 'full': 'var(--border-radius-circle)', // 50% }, + fontFamily: { + sans: 'var(--fonts-regular)', + mono: 'var(--fonts-monospace)', + }, fontWeight: { light: 'var(--font-weight-light)', normal: 'var(--font-weight-normal)', diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl index 218776182..687a277a8 100644 --- a/templates/admin/auth/edit.tmpl +++ b/templates/admin/auth/edit.tmpl @@ -42,7 +42,7 @@ -
+
@@ -113,7 +113,7 @@
-
+
@@ -148,7 +148,7 @@
-
+
@@ -205,7 +205,7 @@

{{ctx.Locale.Tr "admin.auths.force_smtps_helper"}}

-
+
diff --git a/templates/admin/auth/new.tmpl b/templates/admin/auth/new.tmpl index 255eac202..f6a14e1f7 100644 --- a/templates/admin/auth/new.tmpl +++ b/templates/admin/auth/new.tmpl @@ -33,13 +33,13 @@ {{template "admin/auth/source/smtp" .}} -
+
-
+
@@ -59,7 +59,7 @@
-
+
diff --git a/templates/admin/auth/source/ldap.tmpl b/templates/admin/auth/source/ldap.tmpl index b945a1ab1..680849c8e 100644 --- a/templates/admin/auth/source/ldap.tmpl +++ b/templates/admin/auth/source/ldap.tmpl @@ -1,4 +1,4 @@ -
+
-
+
-
+
-
+
@@ -38,7 +38,7 @@
-
+
@@ -115,13 +115,13 @@
-
+
-
+
diff --git a/templates/admin/auth/source/oauth.tmpl b/templates/admin/auth/source/oauth.tmpl index da3a4f913..0560cc825 100644 --- a/templates/admin/auth/source/oauth.tmpl +++ b/templates/admin/auth/source/oauth.tmpl @@ -1,4 +1,4 @@ -
+