mirror of https://github.com/go-gitea/gitea.git
Merge branch 'main' into RepoOrderBy-respect-owner-too
This commit is contained in:
commit
19a822c6ed
4
go.mod
4
go.mod
|
@ -8,7 +8,7 @@ require (
|
||||||
code.gitea.io/sdk/gitea v0.17.1
|
code.gitea.io/sdk/gitea v0.17.1
|
||||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
|
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
|
||||||
connectrpc.com/connect v1.15.0
|
connectrpc.com/connect v1.15.0
|
||||||
gitea.com/go-chi/binding v0.0.0-20240316035258-17450c5f3028
|
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed
|
||||||
gitea.com/go-chi/cache v0.2.0
|
gitea.com/go-chi/cache v0.2.0
|
||||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
|
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
|
||||||
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96
|
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96
|
||||||
|
@ -59,6 +59,7 @@ require (
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/feeds v1.1.2
|
github.com/gorilla/feeds v1.1.2
|
||||||
github.com/gorilla/sessions v1.2.2
|
github.com/gorilla/sessions v1.2.2
|
||||||
|
github.com/h2non/gock v1.2.0
|
||||||
github.com/hashicorp/go-version v1.6.0
|
github.com/hashicorp/go-version v1.6.0
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||||
github.com/huandu/xstrings v1.4.0
|
github.com/huandu/xstrings v1.4.0
|
||||||
|
@ -209,6 +210,7 @@ require (
|
||||||
github.com/gorilla/handlers v1.5.2 // indirect
|
github.com/gorilla/handlers v1.5.2 // indirect
|
||||||
github.com/gorilla/mux v1.8.1 // indirect
|
github.com/gorilla/mux v1.8.1 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
|
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
|
|
10
go.sum
10
go.sum
|
@ -20,8 +20,8 @@ git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4H
|
||||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
|
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
|
||||||
gitea.com/gitea/act v0.259.1 h1:8GG1o/xtUHl3qjn5f0h/2FXrT5ubBn05TJOM5ry+FBw=
|
gitea.com/gitea/act v0.259.1 h1:8GG1o/xtUHl3qjn5f0h/2FXrT5ubBn05TJOM5ry+FBw=
|
||||||
gitea.com/gitea/act v0.259.1/go.mod h1:UxZWRYqQG2Yj4+4OqfGWW5a3HELwejyWFQyU7F1jUD8=
|
gitea.com/gitea/act v0.259.1/go.mod h1:UxZWRYqQG2Yj4+4OqfGWW5a3HELwejyWFQyU7F1jUD8=
|
||||||
gitea.com/go-chi/binding v0.0.0-20240316035258-17450c5f3028 h1:6/QAx4+s0dyRwdaTFPTnhGppuiuu0OqxIH9szyTpvKw=
|
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHcgq2mt6NSGhJSZBuduAlinMEmso=
|
||||||
gitea.com/go-chi/binding v0.0.0-20240316035258-17450c5f3028/go.mod h1:E3i3cgB04dDx0v3CytCgRTTn9Z/9x891aet3r456RVw=
|
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed/go.mod h1:E3i3cgB04dDx0v3CytCgRTTn9Z/9x891aet3r456RVw=
|
||||||
gitea.com/go-chi/cache v0.2.0 h1:E0npuTfDW6CT1yD8NMDVc1SK6IeRjfmRL2zlEsCEd7w=
|
gitea.com/go-chi/cache v0.2.0 h1:E0npuTfDW6CT1yD8NMDVc1SK6IeRjfmRL2zlEsCEd7w=
|
||||||
gitea.com/go-chi/cache v0.2.0/go.mod h1:iQlVK2aKTZ/rE9UcHyz9pQWGvdP9i1eI2spOpzgCrtE=
|
gitea.com/go-chi/cache v0.2.0/go.mod h1:iQlVK2aKTZ/rE9UcHyz9pQWGvdP9i1eI2spOpzgCrtE=
|
||||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 h1:p2ki+WK0cIeNQuqjR98IP2KZQKRzJJiV7aTeMAFwaWo=
|
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 h1:p2ki+WK0cIeNQuqjR98IP2KZQKRzJJiV7aTeMAFwaWo=
|
||||||
|
@ -430,6 +430,10 @@ github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pw
|
||||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||||
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
|
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
|
||||||
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
||||||
|
github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
|
||||||
|
github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=
|
||||||
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||||
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||||
|
@ -591,6 +595,8 @@ github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
||||||
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
||||||
github.com/msteinert/pam v1.2.0 h1:mYfjlvN2KYs2Pb9G6nb/1f/nPfAttT/Jee5Sq9r3bGE=
|
github.com/msteinert/pam v1.2.0 h1:mYfjlvN2KYs2Pb9G6nb/1f/nPfAttT/Jee5Sq9r3bGE=
|
||||||
github.com/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0=
|
github.com/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0=
|
||||||
|
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
||||||
|
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||||
github.com/niklasfasching/go-org v1.7.0 h1:vyMdcMWWTe/XmANk19F4k8XGBYg0GQ/gJGMimOjGMek=
|
github.com/niklasfasching/go-org v1.7.0 h1:vyMdcMWWTe/XmANk19F4k8XGBYg0GQ/gJGMimOjGMek=
|
||||||
github.com/niklasfasching/go-org v1.7.0/go.mod h1:WuVm4d45oePiE0eX25GqTDQIt/qPW1T9DGkRscqLW5o=
|
github.com/niklasfasching/go-org v1.7.0/go.mod h1:WuVm4d45oePiE0eX25GqTDQIt/qPW1T9DGkRscqLW5o=
|
||||||
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||||
|
|
|
@ -4,12 +4,11 @@
|
||||||
package pwn
|
package pwn
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand/v2"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/h2non/gock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,86 +17,34 @@ var client = New(WithHTTP(&http.Client{
|
||||||
}))
|
}))
|
||||||
|
|
||||||
func TestPassword(t *testing.T) {
|
func TestPassword(t *testing.T) {
|
||||||
// Check input error
|
defer gock.Off()
|
||||||
_, err := client.CheckPassword("", false)
|
|
||||||
|
count, err := client.CheckPassword("", false)
|
||||||
assert.ErrorIs(t, err, ErrEmptyPassword, "blank input should return ErrEmptyPassword")
|
assert.ErrorIs(t, err, ErrEmptyPassword, "blank input should return ErrEmptyPassword")
|
||||||
|
assert.Equal(t, -1, count)
|
||||||
|
|
||||||
// Should fail
|
gock.New("https://api.pwnedpasswords.com").Get("/range/5c1d8").Times(1).Reply(200).BodyString("EAF2F254732680E8AC339B84F3266ECCBB5:1\r\nFC446EB88938834178CB9322C1EE273C2A7:2")
|
||||||
fail := "password1234"
|
count, err = client.CheckPassword("pwned", false)
|
||||||
count, err := client.CheckPassword(fail, false)
|
|
||||||
assert.NotEmpty(t, count, "%s should fail as a password", fail)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, count)
|
||||||
|
|
||||||
// Should fail (with padding)
|
gock.New("https://api.pwnedpasswords.com").Get("/range/ba189").Times(1).Reply(200).BodyString("FD4CB34F0378BCB15D23F6FFD28F0775C9E:3\r\nFDF342FCD8C3611DAE4D76E8A992A3E4169:4")
|
||||||
failPad := "administrator"
|
count, err = client.CheckPassword("notpwned", false)
|
||||||
count, err = client.CheckPassword(failPad, true)
|
|
||||||
assert.NotEmpty(t, count, "%s should fail as a password", failPad)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, count)
|
||||||
|
|
||||||
// Checking for a "good" password isn't going to be perfect, but we can give it a good try
|
gock.New("https://api.pwnedpasswords.com").Get("/range/a1733").Times(1).Reply(200).BodyString("C4CE0F1F0062B27B9E2F41AF0C08218017C:1\r\nFC446EB88938834178CB9322C1EE273C2A7:2\r\nFE81480327C992FE62065A827429DD1318B:0")
|
||||||
// with hopefully minimal error. Try five times?
|
count, err = client.CheckPassword("paddedpwned", true)
|
||||||
assert.Condition(t, func() bool {
|
assert.NoError(t, err)
|
||||||
for i := 0; i <= 5; i++ {
|
assert.Equal(t, 1, count)
|
||||||
count, err = client.CheckPassword(testPassword(), false)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
if count == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}, "no generated passwords passed. there is a chance this is a fluke")
|
|
||||||
|
|
||||||
// Again, but with padded responses
|
gock.New("https://api.pwnedpasswords.com").Get("/range/5617b").Times(1).Reply(200).BodyString("FD4CB34F0378BCB15D23F6FFD28F0775C9E:3\r\nFDF342FCD8C3611DAE4D76E8A992A3E4169:4\r\nFE81480327C992FE62065A827429DD1318B:0")
|
||||||
assert.Condition(t, func() bool {
|
count, err = client.CheckPassword("paddednotpwned", true)
|
||||||
for i := 0; i <= 5; i++ {
|
assert.NoError(t, err)
|
||||||
count, err = client.CheckPassword(testPassword(), true)
|
assert.Equal(t, 0, count)
|
||||||
assert.NoError(t, err)
|
|
||||||
if count == 0 {
|
gock.New("https://api.pwnedpasswords.com").Get("/range/79082").Times(1).Reply(200).BodyString("FDF342FCD8C3611DAE4D76E8A992A3E4169:4\r\nFE81480327C992FE62065A827429DD1318B:0\r\nAFEF386F56EB0B4BE314E07696E5E6E6536:0")
|
||||||
return true
|
count, err = client.CheckPassword("paddednotpwnedzero", true)
|
||||||
}
|
assert.NoError(t, err)
|
||||||
}
|
assert.Equal(t, 0, count)
|
||||||
return false
|
|
||||||
}, "no generated passwords passed. there is a chance this is a fluke")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Credit to https://golangbyexample.com/generate-random-password-golang/
|
|
||||||
// DO NOT USE THIS FOR AN ACTUAL PASSWORD GENERATOR
|
|
||||||
var (
|
|
||||||
lowerCharSet = "abcdedfghijklmnopqrst"
|
|
||||||
upperCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
||||||
specialCharSet = "!@#$%&*"
|
|
||||||
numberSet = "0123456789"
|
|
||||||
allCharSet = lowerCharSet + upperCharSet + specialCharSet + numberSet
|
|
||||||
)
|
|
||||||
|
|
||||||
func testPassword() string {
|
|
||||||
var password strings.Builder
|
|
||||||
|
|
||||||
// Set special character
|
|
||||||
for i := 0; i < 5; i++ {
|
|
||||||
random := rand.IntN(len(specialCharSet))
|
|
||||||
password.WriteString(string(specialCharSet[random]))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set numeric
|
|
||||||
for i := 0; i < 5; i++ {
|
|
||||||
random := rand.IntN(len(numberSet))
|
|
||||||
password.WriteString(string(numberSet[random]))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set uppercase
|
|
||||||
for i := 0; i < 5; i++ {
|
|
||||||
random := rand.IntN(len(upperCharSet))
|
|
||||||
password.WriteString(string(upperCharSet[random]))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < 5; i++ {
|
|
||||||
random := rand.IntN(len(allCharSet))
|
|
||||||
password.WriteString(string(allCharSet[random]))
|
|
||||||
}
|
|
||||||
inRune := []rune(password.String())
|
|
||||||
rand.Shuffle(len(inRune), func(i, j int) {
|
|
||||||
inRune[i], inRune[j] = inRune[j], inRune[i]
|
|
||||||
})
|
|
||||||
return string(inRune)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
"@github/text-expander-element": "2.6.1",
|
"@github/text-expander-element": "2.6.1",
|
||||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||||
"@primer/octicons": "19.9.0",
|
"@primer/octicons": "19.9.0",
|
||||||
|
"@silverwind/vue3-calendar-heatmap": "2.0.6",
|
||||||
"add-asset-webpack-plugin": "2.0.1",
|
"add-asset-webpack-plugin": "2.0.1",
|
||||||
"ansi_up": "6.0.2",
|
"ansi_up": "6.0.2",
|
||||||
"asciinema-player": "3.7.1",
|
"asciinema-player": "3.7.1",
|
||||||
|
@ -57,7 +58,6 @@
|
||||||
"vue-bar-graph": "2.0.0",
|
"vue-bar-graph": "2.0.0",
|
||||||
"vue-chartjs": "5.3.1",
|
"vue-chartjs": "5.3.1",
|
||||||
"vue-loader": "17.4.2",
|
"vue-loader": "17.4.2",
|
||||||
"vue3-calendar-heatmap": "2.0.5",
|
|
||||||
"webpack": "5.91.0",
|
"webpack": "5.91.0",
|
||||||
"webpack-cli": "5.1.4",
|
"webpack-cli": "5.1.4",
|
||||||
"wrap-ansi": "9.0.0"
|
"wrap-ansi": "9.0.0"
|
||||||
|
@ -1626,6 +1626,18 @@
|
||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@silverwind/vue3-calendar-heatmap": {
|
||||||
|
"version": "2.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@silverwind/vue3-calendar-heatmap/-/vue3-calendar-heatmap-2.0.6.tgz",
|
||||||
|
"integrity": "sha512-efX+nf2GR7EfA7iNgZDeM9Jue5ksglSXvN0C/ja0M1bTmkCpAxKlGJ3vki7wfTPQgX1O0nCfAM62IKqUUEM0cQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"tippy.js": "^6.3.7",
|
||||||
|
"vue": "^3.2.29"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@sinclair/typebox": {
|
"node_modules/@sinclair/typebox": {
|
||||||
"version": "0.27.8",
|
"version": "0.27.8",
|
||||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
||||||
|
@ -12200,18 +12212,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vue3-calendar-heatmap": {
|
|
||||||
"version": "2.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/vue3-calendar-heatmap/-/vue3-calendar-heatmap-2.0.5.tgz",
|
|
||||||
"integrity": "sha512-qvveNQlTS5Aw7AvRLs0zOyu3uP5iGJlXJAnkrkG2ElDdyQ8H1TJhQ8rL702CROjAg16ezIveUY10nCO7lqZ25w==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"tippy.js": "^6.3.7",
|
|
||||||
"vue": "^3.2.29"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/watchpack": {
|
"node_modules/watchpack": {
|
||||||
"version": "2.4.1",
|
"version": "2.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz",
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
"@github/text-expander-element": "2.6.1",
|
"@github/text-expander-element": "2.6.1",
|
||||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||||
"@primer/octicons": "19.9.0",
|
"@primer/octicons": "19.9.0",
|
||||||
|
"@silverwind/vue3-calendar-heatmap": "2.0.6",
|
||||||
"add-asset-webpack-plugin": "2.0.1",
|
"add-asset-webpack-plugin": "2.0.1",
|
||||||
"ansi_up": "6.0.2",
|
"ansi_up": "6.0.2",
|
||||||
"asciinema-player": "3.7.1",
|
"asciinema-player": "3.7.1",
|
||||||
|
@ -56,7 +57,6 @@
|
||||||
"vue-bar-graph": "2.0.0",
|
"vue-bar-graph": "2.0.0",
|
||||||
"vue-chartjs": "5.3.1",
|
"vue-chartjs": "5.3.1",
|
||||||
"vue-loader": "17.4.2",
|
"vue-loader": "17.4.2",
|
||||||
"vue3-calendar-heatmap": "2.0.5",
|
|
||||||
"webpack": "5.91.0",
|
"webpack": "5.91.0",
|
||||||
"webpack-cli": "5.1.4",
|
"webpack-cli": "5.1.4",
|
||||||
"wrap-ansi": "9.0.0"
|
"wrap-ansi": "9.0.0"
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
actions_service "code.gitea.io/gitea/services/actions"
|
actions_service "code.gitea.io/gitea/services/actions"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
|
"code.gitea.io/gitea/services/convert"
|
||||||
secret_service "code.gitea.io/gitea/services/secrets"
|
secret_service "code.gitea.io/gitea/services/secrets"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -517,3 +518,68 @@ type Action struct{}
|
||||||
func NewAction() actions_service.API {
|
func NewAction() actions_service.API {
|
||||||
return Action{}
|
return Action{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListActionTasks list all the actions of a repository
|
||||||
|
func ListActionTasks(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/actions/tasks repository ListActionTasks
|
||||||
|
// ---
|
||||||
|
// summary: List a repository's action tasks
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: page
|
||||||
|
// in: query
|
||||||
|
// description: page number of results to return (1-based)
|
||||||
|
// type: integer
|
||||||
|
// - name: limit
|
||||||
|
// in: query
|
||||||
|
// description: page size of results, default maximum page size is 50
|
||||||
|
// type: integer
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/TasksList"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "409":
|
||||||
|
// "$ref": "#/responses/conflict"
|
||||||
|
// "422":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
|
|
||||||
|
tasks, total, err := db.FindAndCount[actions_model.ActionTask](ctx, &actions_model.FindTaskOptions{
|
||||||
|
ListOptions: utils.GetListOptions(ctx),
|
||||||
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "ListActionTasks", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := new(api.ActionTaskResponse)
|
||||||
|
res.TotalCount = total
|
||||||
|
|
||||||
|
res.Entries = make([]*api.ActionTask, len(tasks))
|
||||||
|
for i := range tasks {
|
||||||
|
convertedTask, err := convert.ToActionTask(ctx, tasks[i])
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "ToActionTask", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res.Entries[i] = convertedTask
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, &res)
|
||||||
|
}
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package repo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
|
||||||
"code.gitea.io/gitea/models/db"
|
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
|
||||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
|
||||||
"code.gitea.io/gitea/services/context"
|
|
||||||
"code.gitea.io/gitea/services/convert"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListActionTasks list all the actions of a repository
|
|
||||||
func ListActionTasks(ctx *context.APIContext) {
|
|
||||||
// swagger:operation GET /repos/{owner}/{repo}/actions/tasks repository ListActionTasks
|
|
||||||
// ---
|
|
||||||
// summary: List a repository's action tasks
|
|
||||||
// produces:
|
|
||||||
// - application/json
|
|
||||||
// parameters:
|
|
||||||
// - name: owner
|
|
||||||
// in: path
|
|
||||||
// description: owner of the repo
|
|
||||||
// type: string
|
|
||||||
// required: true
|
|
||||||
// - name: repo
|
|
||||||
// in: path
|
|
||||||
// description: name of the repo
|
|
||||||
// type: string
|
|
||||||
// required: true
|
|
||||||
// - name: page
|
|
||||||
// in: query
|
|
||||||
// description: page number of results to return (1-based)
|
|
||||||
// type: integer
|
|
||||||
// - name: limit
|
|
||||||
// in: query
|
|
||||||
// description: page size of results, default maximum page size is 50
|
|
||||||
// type: integer
|
|
||||||
// responses:
|
|
||||||
// "200":
|
|
||||||
// "$ref": "#/responses/TasksList"
|
|
||||||
// "400":
|
|
||||||
// "$ref": "#/responses/error"
|
|
||||||
// "403":
|
|
||||||
// "$ref": "#/responses/forbidden"
|
|
||||||
// "404":
|
|
||||||
// "$ref": "#/responses/notFound"
|
|
||||||
// "409":
|
|
||||||
// "$ref": "#/responses/conflict"
|
|
||||||
// "422":
|
|
||||||
// "$ref": "#/responses/validationError"
|
|
||||||
|
|
||||||
tasks, total, err := db.FindAndCount[actions_model.ActionTask](ctx, &actions_model.FindTaskOptions{
|
|
||||||
ListOptions: utils.GetListOptions(ctx),
|
|
||||||
RepoID: ctx.Repo.Repository.ID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
ctx.Error(http.StatusInternalServerError, "ListActionTasks", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res := new(api.ActionTaskResponse)
|
|
||||||
res.TotalCount = total
|
|
||||||
|
|
||||||
res.Entries = make([]*api.ActionTask, len(tasks))
|
|
||||||
for i := range tasks {
|
|
||||||
convertedTask, err := convert.ToActionTask(ctx, tasks[i])
|
|
||||||
if err != nil {
|
|
||||||
ctx.Error(http.StatusInternalServerError, "ToActionTask", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
res.Entries[i] = convertedTask
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, &res)
|
|
||||||
}
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/services/attachment"
|
"code.gitea.io/gitea/services/attachment"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
|
"code.gitea.io/gitea/services/context/upload"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
issue_service "code.gitea.io/gitea/services/issue"
|
issue_service "code.gitea.io/gitea/services/issue"
|
||||||
)
|
)
|
||||||
|
@ -153,6 +154,8 @@ func CreateIssueAttachment(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
|
// "422":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
// "423":
|
// "423":
|
||||||
// "$ref": "#/responses/repoArchivedError"
|
// "$ref": "#/responses/repoArchivedError"
|
||||||
|
|
||||||
|
@ -185,7 +188,11 @@ func CreateIssueAttachment(ctx *context.APIContext) {
|
||||||
IssueID: issue.ID,
|
IssueID: issue.ID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "UploadAttachment", err)
|
if upload.IsErrFileTypeForbidden(err) {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, "", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "UploadAttachment", err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/services/attachment"
|
"code.gitea.io/gitea/services/attachment"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
|
"code.gitea.io/gitea/services/context/upload"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
issue_service "code.gitea.io/gitea/services/issue"
|
issue_service "code.gitea.io/gitea/services/issue"
|
||||||
)
|
)
|
||||||
|
@ -160,6 +161,8 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/forbidden"
|
// "$ref": "#/responses/forbidden"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
|
// "422":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
// "423":
|
// "423":
|
||||||
// "$ref": "#/responses/repoArchivedError"
|
// "$ref": "#/responses/repoArchivedError"
|
||||||
|
|
||||||
|
@ -194,9 +197,14 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
|
||||||
CommentID: comment.ID,
|
CommentID: comment.ID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "UploadAttachment", err)
|
if upload.IsErrFileTypeForbidden(err) {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, "", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "UploadAttachment", err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := comment.LoadAttachments(ctx); err != nil {
|
if err := comment.LoadAttachments(ctx); err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "LoadAttachments", err)
|
ctx.Error(http.StatusInternalServerError, "LoadAttachments", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -470,8 +470,9 @@ func AuthorizeOAuth(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect if user already granted access
|
// Redirect if user already granted access and the application is confidential.
|
||||||
if grant != nil {
|
// I.e. always require authorization for public clients as recommended by RFC 6749 Section 10.2
|
||||||
|
if app.ConfidentialClient && grant != nil {
|
||||||
code, err := grant.GenerateNewAuthorizationCode(ctx, form.RedirectURI, form.CodeChallenge, form.CodeChallengeMethod)
|
code, err := grant.GenerateNewAuthorizationCode(ctx, form.RedirectURI, form.CodeChallenge, form.CodeChallengeMethod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleServerError(ctx, form.State, form.RedirectURI)
|
handleServerError(ctx, form.State, form.RedirectURI)
|
||||||
|
|
|
@ -289,8 +289,8 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure to compose independent messages to avoid leaking user emails
|
// Make sure to compose independent messages to avoid leaking user emails
|
||||||
msgID := createReference(ctx.Issue, ctx.Comment, ctx.ActionType)
|
msgID := generateMessageIDForIssue(ctx.Issue, ctx.Comment, ctx.ActionType)
|
||||||
reference := createReference(ctx.Issue, nil, activities_model.ActionType(0))
|
reference := generateMessageIDForIssue(ctx.Issue, nil, activities_model.ActionType(0))
|
||||||
|
|
||||||
var replyPayload []byte
|
var replyPayload []byte
|
||||||
if ctx.Comment != nil {
|
if ctx.Comment != nil {
|
||||||
|
@ -362,7 +362,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
|
||||||
return msgs, nil
|
return msgs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createReference(issue *issues_model.Issue, comment *issues_model.Comment, actionType activities_model.ActionType) string {
|
func generateMessageIDForIssue(issue *issues_model.Issue, comment *issues_model.Comment, actionType activities_model.ActionType) string {
|
||||||
var path string
|
var path string
|
||||||
if issue.IsPull {
|
if issue.IsPull {
|
||||||
path = "pulls"
|
path = "pulls"
|
||||||
|
@ -389,6 +389,10 @@ func createReference(issue *issues_model.Issue, comment *issues_model.Comment, a
|
||||||
return fmt.Sprintf("<%s/%s/%d%s@%s>", issue.Repo.FullName(), path, issue.Index, extra, setting.Domain)
|
return fmt.Sprintf("<%s/%s/%d%s@%s>", issue.Repo.FullName(), path, issue.Index, extra, setting.Domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func generateMessageIDForRelease(release *repo_model.Release) string {
|
||||||
|
return fmt.Sprintf("<%s/releases/%d@%s>", release.Repo.FullName(), release.ID, setting.Domain)
|
||||||
|
}
|
||||||
|
|
||||||
func generateAdditionalHeaders(ctx *mailCommentContext, reason string, recipient *user_model.User) map[string]string {
|
func generateAdditionalHeaders(ctx *mailCommentContext, reason string, recipient *user_model.User) map[string]string {
|
||||||
repo := ctx.Issue.Repo
|
repo := ctx.Issue.Repo
|
||||||
|
|
||||||
|
|
|
@ -86,11 +86,11 @@ func mailNewRelease(ctx context.Context, lang string, tos []string, rel *repo_mo
|
||||||
|
|
||||||
msgs := make([]*Message, 0, len(tos))
|
msgs := make([]*Message, 0, len(tos))
|
||||||
publisherName := rel.Publisher.DisplayName()
|
publisherName := rel.Publisher.DisplayName()
|
||||||
relURL := "<" + rel.HTMLURL() + ">"
|
msgID := generateMessageIDForRelease(rel)
|
||||||
for _, to := range tos {
|
for _, to := range tos {
|
||||||
msg := NewMessageFrom(to, publisherName, setting.MailService.FromEmail, subject, mailBody.String())
|
msg := NewMessageFrom(to, publisherName, setting.MailService.FromEmail, subject, mailBody.String())
|
||||||
msg.Info = subject
|
msg.Info = subject
|
||||||
msg.SetHeader("Message-ID", relURL)
|
msg.SetHeader("Message-ID", msgID)
|
||||||
msgs = append(msgs, msg)
|
msgs = append(msgs, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -288,7 +288,7 @@ func TestGenerateAdditionalHeaders(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_createReference(t *testing.T) {
|
func TestGenerateMessageIDForIssue(t *testing.T) {
|
||||||
_, _, issue, comment := prepareMailerTest(t)
|
_, _, issue, comment := prepareMailerTest(t)
|
||||||
_, _, pullIssue, _ := prepareMailerTest(t)
|
_, _, pullIssue, _ := prepareMailerTest(t)
|
||||||
pullIssue.IsPull = true
|
pullIssue.IsPull = true
|
||||||
|
@ -388,10 +388,18 @@ func Test_createReference(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got := createReference(tt.args.issue, tt.args.comment, tt.args.actionType)
|
got := generateMessageIDForIssue(tt.args.issue, tt.args.comment, tt.args.actionType)
|
||||||
if !strings.HasPrefix(got, tt.prefix) {
|
if !strings.HasPrefix(got, tt.prefix) {
|
||||||
t.Errorf("createReference() = %v, want %v", got, tt.prefix)
|
t.Errorf("generateMessageIDForIssue() = %v, want %v", got, tt.prefix)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGenerateMessageIDForRelease(t *testing.T) {
|
||||||
|
msgID := generateMessageIDForRelease(&repo_model.Release{
|
||||||
|
ID: 1,
|
||||||
|
Repo: &repo_model.Repository{OwnerName: "owner", Name: "repo"},
|
||||||
|
})
|
||||||
|
assert.Equal(t, "<owner/repo/releases/1@localhost>", msgID)
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
SSH
|
SSH
|
||||||
</button>
|
</button>
|
||||||
{{end}}
|
{{end}}
|
||||||
<input id="repo-clone-url" size="20" class="js-clone-url" value="{{$.CloneButtonOriginLink.HTTPS}}" readonly>
|
<input id="repo-clone-url" size="10" class="js-clone-url" value="{{$.CloneButtonOriginLink.HTTPS}}" readonly>
|
||||||
<button class="ui small icon button" id="clipboard-btn" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}" data-clipboard-target="#repo-clone-url" aria-label="{{ctx.Locale.Tr "copy_url"}}">
|
<button class="ui small icon button" id="clipboard-btn" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}" data-clipboard-target="#repo-clone-url" aria-label="{{ctx.Locale.Tr "copy_url"}}">
|
||||||
{{svg "octicon-copy" 14}}
|
{{svg "octicon-copy" 14}}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
{{$l := Eval $n "-" 1}}
|
{{$l := Eval $n "-" 1}}
|
||||||
{{$isHomepage := (eq $n 0)}}
|
{{$isHomepage := (eq $n 0)}}
|
||||||
<div class="repo-button-row">
|
<div class="repo-button-row">
|
||||||
<div class="tw-flex tw-items-center tw-flex-wrap tw-gap-y-2">
|
<div class="repo-button-row-left">
|
||||||
{{template "repo/branch_dropdown" dict "root" . "ContainerClasses" "tw-mr-1"}}
|
{{template "repo/branch_dropdown" dict "root" . "ContainerClasses" "tw-mr-1"}}
|
||||||
{{if and .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}}
|
{{if and .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}}
|
||||||
{{$cmpBranch := ""}}
|
{{$cmpBranch := ""}}
|
||||||
|
@ -66,7 +66,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if and .CanWriteCode .IsViewBranch (not .Repository.IsMirror) (not .Repository.IsArchived) (not .IsViewFile)}}
|
{{if and .CanWriteCode .IsViewBranch (not .Repository.IsMirror) (not .Repository.IsArchived) (not .IsViewFile)}}
|
||||||
<button class="ui dropdown basic compact jump button tw-mr-1"{{if not .Repository.CanEnableEditor}} disabled{{end}}>
|
<button class="ui dropdown basic compact jump button"{{if not .Repository.CanEnableEditor}} disabled{{end}}>
|
||||||
{{ctx.Locale.Tr "repo.editor.add_file"}}
|
{{ctx.Locale.Tr "repo.editor.add_file"}}
|
||||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
|
@ -93,9 +93,9 @@
|
||||||
{{if $isHomepage}}
|
{{if $isHomepage}}
|
||||||
{{/* only show the "code search" on the repo home page, it only does global search,
|
{{/* only show the "code search" on the repo home page, it only does global search,
|
||||||
so do not show it when viewing file or directory to avoid misleading users (it doesn't search in a directory) */}}
|
so do not show it when viewing file or directory to avoid misleading users (it doesn't search in a directory) */}}
|
||||||
<form class="ignore-dirty" action="{{.RepoLink}}/search" method="get">
|
<form class="ignore-dirty tw-flex tw-flex-1" action="{{.RepoLink}}/search" method="get">
|
||||||
<div class="ui small action input">
|
<div class="ui small action input tw-flex-1">
|
||||||
<input name="q" placeholder="{{ctx.Locale.Tr "search.code_kind"}}">
|
<input name="q" size="10" placeholder="{{ctx.Locale.Tr "search.code_kind"}}">
|
||||||
{{template "shared/search/button"}}
|
{{template "shared/search/button"}}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -113,7 +113,7 @@
|
||||||
</span>
|
</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="tw-flex tw-items-center">
|
<div class="repo-button-row-right">
|
||||||
<!-- Only show clone panel in repository home page -->
|
<!-- Only show clone panel in repository home page -->
|
||||||
{{if $isHomepage}}
|
{{if $isHomepage}}
|
||||||
<div class="clone-panel ui action tiny input">
|
<div class="clone-panel ui action tiny input">
|
||||||
|
|
|
@ -7478,6 +7478,9 @@
|
||||||
"404": {
|
"404": {
|
||||||
"$ref": "#/responses/error"
|
"$ref": "#/responses/error"
|
||||||
},
|
},
|
||||||
|
"422": {
|
||||||
|
"$ref": "#/responses/validationError"
|
||||||
|
},
|
||||||
"423": {
|
"423": {
|
||||||
"$ref": "#/responses/repoArchivedError"
|
"$ref": "#/responses/repoArchivedError"
|
||||||
}
|
}
|
||||||
|
@ -8097,6 +8100,9 @@
|
||||||
"404": {
|
"404": {
|
||||||
"$ref": "#/responses/error"
|
"$ref": "#/responses/error"
|
||||||
},
|
},
|
||||||
|
"422": {
|
||||||
|
"$ref": "#/responses/validationError"
|
||||||
|
},
|
||||||
"423": {
|
"423": {
|
||||||
"$ref": "#/responses/repoArchivedError"
|
"$ref": "#/responses/repoArchivedError"
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,6 +120,34 @@ func TestAPICreateCommentAttachment(t *testing.T) {
|
||||||
unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, CommentID: comment.ID})
|
unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, CommentID: comment.ID})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPICreateCommentAttachmentWithUnallowedFile(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2})
|
||||||
|
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID})
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
|
||||||
|
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||||
|
|
||||||
|
session := loginUser(t, repoOwner.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
|
||||||
|
|
||||||
|
filename := "file.bad"
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
|
||||||
|
// Setup multi-part.
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
_, err := writer.CreateFormFile("attachment", filename)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = writer.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets", repoOwner.Name, repo.Name, comment.ID), body).
|
||||||
|
AddTokenAuth(token).
|
||||||
|
SetHeader("Content-Type", writer.FormDataContentType())
|
||||||
|
|
||||||
|
session.MakeRequest(t, req, http.StatusUnprocessableEntity)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAPIEditCommentAttachment(t *testing.T) {
|
func TestAPIEditCommentAttachment(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
|
|
@ -96,6 +96,33 @@ func TestAPICreateIssueAttachment(t *testing.T) {
|
||||||
unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, IssueID: issue.ID})
|
unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, IssueID: issue.ID})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPICreateIssueAttachmentWithUnallowedFile(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID})
|
||||||
|
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||||
|
|
||||||
|
session := loginUser(t, repoOwner.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
|
||||||
|
|
||||||
|
filename := "file.bad"
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
|
||||||
|
// Setup multi-part.
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
_, err := writer.CreateFormFile("attachment", filename)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = writer.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets", repoOwner.Name, repo.Name, issue.Index), body).
|
||||||
|
AddTokenAuth(token)
|
||||||
|
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||||
|
|
||||||
|
session.MakeRequest(t, req, http.StatusUnprocessableEntity)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAPIEditIssueAttachment(t *testing.T) {
|
func TestAPIEditIssueAttachment(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,10 @@
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#user-heatmap .vch__day__square:hover {
|
||||||
|
outline: 1.5px solid var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
/* move the "? contributions in the last ? months" text from top to bottom */
|
/* move the "? contributions in the last ? months" text from top to bottom */
|
||||||
#user-heatmap .total-contributions {
|
#user-heatmap .total-contributions {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
|
|
@ -188,8 +188,8 @@
|
||||||
.ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection:hover,
|
.ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection:hover,
|
||||||
.ui.action.input:not([class*="left action"]) > input:focus + .button,
|
.ui.action.input:not([class*="left action"]) > input:focus + .button,
|
||||||
.ui.action.input:not([class*="left action"]) > input:focus + .button:hover,
|
.ui.action.input:not([class*="left action"]) > input:focus + .button:hover,
|
||||||
.ui.action.input:not([class*="left action"]) > input:focus + .icon + .button,
|
.ui.action.input:not([class*="left action"]) > input:focus + i.icon + .button,
|
||||||
.ui.action.input:not([class*="left action"]) > input:focus + .icon + .button:hover {
|
.ui.action.input:not([class*="left action"]) > input:focus + i.icon + .button:hover {
|
||||||
border-left-color: var(--color-primary);
|
border-left-color: var(--color-primary);
|
||||||
}
|
}
|
||||||
.ui.action.input:not([class*="left action"]) > input:focus {
|
.ui.action.input:not([class*="left action"]) > input:focus {
|
||||||
|
|
|
@ -128,15 +128,22 @@
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repository .clone-panel #repo-clone-url {
|
.repository .clone-panel {
|
||||||
width: 320px;
|
display: flex;
|
||||||
border-radius: 0;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 991.98px) {
|
.repository.wiki .clone-panel {
|
||||||
.repository .clone-panel #repo-clone-url {
|
flex: 0;
|
||||||
width: 200px;
|
}
|
||||||
}
|
|
||||||
|
.repository.wiki .clone-panel input {
|
||||||
|
width: 20ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repository .clone-panel #repo-clone-url {
|
||||||
|
border-radius: 0;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repository .ui.action.input.clone-panel > button + button,
|
.repository .ui.action.input.clone-panel > button + button,
|
||||||
|
@ -2229,17 +2236,37 @@ td .commit-summary {
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo-button-row {
|
.repo-button-row {
|
||||||
margin: 10px 0;
|
margin: 8px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5em;
|
gap: 8px;
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.repo-button-row-left,
|
||||||
|
.repo-button-row-right {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-button-row-right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 991px) {
|
||||||
|
.repository:not(.wiki) .repo-button-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.repo-button-row .button {
|
.repo-button-row .button {
|
||||||
padding: 6px 10px !important;
|
padding: 6px 10px !important;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo-button-row .button.dropdown:not(.icon) {
|
.repo-button-row .button.dropdown:not(.icon) {
|
||||||
|
@ -2250,6 +2277,12 @@ td .commit-summary {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.repo-button-row-left {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tbody.commit-list {
|
tbody.commit-list {
|
||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,4 +18,5 @@ rules:
|
||||||
vue/attributes-order: [0]
|
vue/attributes-order: [0]
|
||||||
vue/html-closing-bracket-spacing: [2, {startTag: never, endTag: never, selfClosingTag: never}]
|
vue/html-closing-bracket-spacing: [2, {startTag: never, endTag: never, selfClosingTag: never}]
|
||||||
vue/max-attributes-per-line: [0]
|
vue/max-attributes-per-line: [0]
|
||||||
|
vue/singleline-html-element-content-newline: [0]
|
||||||
vue-scoped-css/enforce-style-type: [0]
|
vue-scoped-css/enforce-style-type: [0]
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import {CalendarHeatmap} from 'vue3-calendar-heatmap';
|
// TODO: Switch to upstream after https://github.com/razorness/vue3-calendar-heatmap/pull/34 is merged
|
||||||
|
import {CalendarHeatmap} from '@silverwind/vue3-calendar-heatmap';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {CalendarHeatmap},
|
components: {CalendarHeatmap},
|
||||||
|
@ -55,15 +56,16 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="total-contributions">
|
<div class="total-contributions">
|
||||||
{{ locale.contributions_in_the_last_12_months }}
|
{{ locale.textTotalContributions }}
|
||||||
</div>
|
</div>
|
||||||
<calendar-heatmap
|
<calendar-heatmap
|
||||||
:locale="locale"
|
:locale="locale.heatMapLocale"
|
||||||
:no-data-text="locale.no_contributions"
|
:no-data-text="locale.noDataText"
|
||||||
:tooltip-unit="locale.contributions"
|
:tooltip-unit="locale.tooltipUnit"
|
||||||
:end-date="endDate"
|
:end-date="endDate"
|
||||||
:values="values"
|
:values="values"
|
||||||
:range-color="colorRange"
|
:range-color="colorRange"
|
||||||
@day-click="handleDayClick($event)"
|
@day-click="handleDayClick($event)"
|
||||||
|
:tippy-props="{theme: 'tooltip'}"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -91,16 +91,22 @@ export default {
|
||||||
<template>
|
<template>
|
||||||
<div ref="root">
|
<div ref="root">
|
||||||
<div v-if="loading" class="tw-h-12 tw-w-12 is-loading"/>
|
<div v-if="loading" class="tw-h-12 tw-w-12 is-loading"/>
|
||||||
<div v-if="!loading && issue !== null">
|
<div v-if="!loading && issue !== null" class="tw-flex tw-flex-col tw-gap-2">
|
||||||
<p><small>{{ issue.repository.full_name }} on {{ createdAt }}</small></p>
|
<div class="tw-text-12">{{ issue.repository.full_name }} on {{ createdAt }}</div>
|
||||||
<p><svg-icon :name="icon" :class="['text', color]"/> <strong>{{ issue.title }}</strong> #{{ issue.number }}</p>
|
<div class="flex-text-block">
|
||||||
<p>{{ body }}</p>
|
<svg-icon :name="icon" :class="['text', color]"/>
|
||||||
|
<span class="issue-title tw-font-semibold tw-break-anywhere">
|
||||||
|
{{ issue.title }}
|
||||||
|
<span class="index">#{{ issue.number }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="body">{{ body }}</div>
|
||||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
<div v-html="renderedLabels"/>
|
<div v-if="issue.labels.length" v-html="renderedLabels"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!loading && issue === null">
|
<div class="tw-flex tw-flex-col tw-gap-2" v-if="!loading && issue === null">
|
||||||
<p><small>{{ i18nErrorOccurred }}</small></p>
|
<div class="tw-text-12">{{ i18nErrorOccurred }}</div>
|
||||||
<p>{{ i18nErrorMessage }}</p>
|
<div>{{ i18nErrorMessage }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -20,13 +20,16 @@ export function initHeatmap() {
|
||||||
|
|
||||||
// last heatmap tooltip localization attempt https://github.com/go-gitea/gitea/pull/24131/commits/a83761cbbae3c2e3b4bced71e680f44432073ac8
|
// last heatmap tooltip localization attempt https://github.com/go-gitea/gitea/pull/24131/commits/a83761cbbae3c2e3b4bced71e680f44432073ac8
|
||||||
const locale = {
|
const locale = {
|
||||||
months: new Array(12).fill().map((_, idx) => translateMonth(idx)),
|
heatMapLocale: {
|
||||||
days: new Array(7).fill().map((_, idx) => translateDay(idx)),
|
months: new Array(12).fill().map((_, idx) => translateMonth(idx)),
|
||||||
contributions: 'contributions',
|
days: new Array(7).fill().map((_, idx) => translateDay(idx)),
|
||||||
contributions_in_the_last_12_months: el.getAttribute('data-locale-total-contributions'),
|
on: ' - ', // no correct locale support for it, because in many languages the sentence is not "something on someday"
|
||||||
no_contributions: el.getAttribute('data-locale-no-contributions'),
|
more: el.getAttribute('data-locale-more'),
|
||||||
more: el.getAttribute('data-locale-more'),
|
less: el.getAttribute('data-locale-less'),
|
||||||
less: el.getAttribute('data-locale-less'),
|
},
|
||||||
|
tooltipUnit: 'contributions',
|
||||||
|
textTotalContributions: el.getAttribute('data-locale-total-contributions'),
|
||||||
|
noDataText: el.getAttribute('data-locale-no-contributions'),
|
||||||
};
|
};
|
||||||
|
|
||||||
const View = createApp(ActivityHeatmap, {values, locale});
|
const View = createApp(ActivityHeatmap, {values, locale});
|
||||||
|
|
Loading…
Reference in New Issue