merge upstream
This commit is contained in:
Generated
-5
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@ go 1.26.3
|
||||
require (
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
|
||||
connectrpc.com/connect v1.20.0
|
||||
gitea.com/gitea/runner v1.0.5
|
||||
gitea.com/gitea/runner v1.0.6
|
||||
gitea.com/go-chi/binding v0.0.0-20260414111559-654cea7ac60a
|
||||
gitea.com/go-chi/cache v0.2.1
|
||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
|
||||
@@ -24,7 +24,7 @@ require (
|
||||
github.com/PuerkitoBio/goquery v1.12.0
|
||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.10.0
|
||||
github.com/alecthomas/chroma/v2 v2.25.0
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.16
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.17
|
||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.14
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
||||
github.com/blevesearch/bleve/v2 v2.6.0
|
||||
@@ -42,9 +42,9 @@ require (
|
||||
github.com/ethantkoenig/rupture v1.0.1
|
||||
github.com/felixge/fgprof v0.9.5
|
||||
github.com/fsnotify/fsnotify v1.10.1
|
||||
github.com/getkin/kin-openapi v0.138.0
|
||||
github.com/getkin/kin-openapi v0.139.0
|
||||
github.com/gliderlabs/ssh v0.3.8
|
||||
github.com/go-chi/chi/v5 v5.2.5
|
||||
github.com/go-chi/chi/v5 v5.3.0
|
||||
github.com/go-chi/cors v1.2.2
|
||||
github.com/go-co-op/gocron/v2 v2.21.2
|
||||
github.com/go-enry/go-enry/v2 v2.9.6
|
||||
@@ -53,7 +53,7 @@ require (
|
||||
github.com/go-ldap/ldap/v3 v3.4.13
|
||||
github.com/go-redsync/redsync/v4 v4.16.0
|
||||
github.com/go-sql-driver/mysql v1.10.0
|
||||
github.com/go-webauthn/webauthn v0.17.3
|
||||
github.com/go-webauthn/webauthn v0.17.4
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
|
||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
@@ -78,7 +78,7 @@ require (
|
||||
github.com/mholt/archives v0.1.5
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
github.com/microsoft/go-mssqldb v1.9.7
|
||||
github.com/minio/minio-go/v7 v7.1.0
|
||||
github.com/minio/minio-go/v7 v7.2.0
|
||||
github.com/msteinert/pam/v2 v2.1.0
|
||||
github.com/niklasfasching/go-org v1.9.1
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
@@ -101,7 +101,7 @@ require (
|
||||
github.com/yohcop/openid-go v1.0.1
|
||||
github.com/yuin/goldmark v1.8.2
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||
gitlab.com/gitlab-org/api/client-go/v2 v2.30.0
|
||||
gitlab.com/gitlab-org/api/client-go/v2 v2.34.0
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.3
|
||||
golang.org/x/crypto v0.52.0
|
||||
golang.org/x/image v0.41.0
|
||||
@@ -176,7 +176,6 @@ require (
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||
github.com/dlclark/regexp2 v1.12.0 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
|
||||
github.com/fatih/color v1.19.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.2 // indirect
|
||||
@@ -185,11 +184,10 @@ require (
|
||||
github.com/go-enry/go-oniguruma v1.2.1 // indirect
|
||||
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/go-webauthn/x v0.2.5 // indirect
|
||||
github.com/go-webauthn/x v0.2.6 // indirect
|
||||
github.com/goccy/go-json v0.10.6 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
@@ -233,8 +231,8 @@ require (
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.2.2 // indirect
|
||||
github.com/oasdiff/yaml v0.0.9 // indirect
|
||||
github.com/oasdiff/yaml3 v0.0.12 // indirect
|
||||
github.com/oasdiff/yaml v0.1.0 // indirect
|
||||
github.com/oasdiff/yaml3 v0.0.13 // indirect
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
|
||||
github.com/olekukonko/errors v1.3.0 // indirect
|
||||
github.com/olekukonko/ll v0.1.8 // indirect
|
||||
|
||||
@@ -10,8 +10,8 @@ dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||
gitea.com/gitea/runner v1.0.5 h1:9Idm8XXxXp4aHoLp0Ba0AEwv87YUblp9gk4qbih+DlE=
|
||||
gitea.com/gitea/runner v1.0.5/go.mod h1:ff8SKVat/6FywBGpN4dKi2x3AlHZUfLOA/vVWHf7+Cw=
|
||||
gitea.com/gitea/runner v1.0.6 h1:QG0YB97z1S7bu3q5VX5sOEs5gyER6AG/oSGFS1dJ1Hg=
|
||||
gitea.com/gitea/runner v1.0.6/go.mod h1:q+WGNAMeOZL4fRqvBTbAFgR2tupWtzLOrzoyKcky3QQ=
|
||||
gitea.com/go-chi/binding v0.0.0-20260414111559-654cea7ac60a h1:JHoBrfuTSF9Ke9aNfSYj1XRPBHjKPgCApVprnt2Am0M=
|
||||
gitea.com/go-chi/binding v0.0.0-20260414111559-654cea7ac60a/go.mod h1:FOsLJIMdpiHzBp3Vby6Wfkdw2ppGscrjgU1IC7E4/zQ=
|
||||
gitea.com/go-chi/cache v0.2.1 h1:bfAPkvXlbcZxPCpcmDVCWoHgiBSBmZN/QosnZvEC0+g=
|
||||
@@ -94,8 +94,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.16 h1:r3RJBuU7X9ibt8RHbMjWE6y60QbKBiII6wSrXnapxSU=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.16/go.mod h1:6cx7zqDENJDbBIIWX6P8s0h6hqHC8Avbjh9Dseo27ug=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.17 h1:gP2nkGsS+KMvF/jfFz2Vv2qiiOqWKyPACSzPsqHgoW8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.17/go.mod h1:Bsew3S/moG5iT77giPj1q8wb/s0RE5/QfH+ASjYtuQc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23/go.mod h1:xYWD6BS9ywC5bS3sz9Xh04whO/hzK2plt2Zkyrp4JuA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 h1:bpd8vxhlQi2r1hiueOw02f/duEPTMK59Q4QMAoTTtTo=
|
||||
@@ -274,8 +274,8 @@ github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx5
|
||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||
github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78=
|
||||
github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/getkin/kin-openapi v0.138.0 h1:ebfE0JAmF6AqHrNBy1KO3Fs68K9tPs48HalvLPo7Rv4=
|
||||
github.com/getkin/kin-openapi v0.138.0/go.mod h1:vUYWaKyMqj7PfTybelXtLuLN9tReS12vxnzMRK+z2GY=
|
||||
github.com/getkin/kin-openapi v0.139.0 h1:pBFXcZJFwz9J1X64jzxlOoNgFm+TF7kNrs9+HJVN6Ic=
|
||||
github.com/getkin/kin-openapi v0.139.0/go.mod h1:NGxPfE4PwS/TRXEbyx2RrxDFPZvxcWw31Tw8XXjPZLs=
|
||||
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 h1:mtDjlmloH7ytdblogrMz1/8Hqua1y8B4ID+bh3rvod0=
|
||||
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A=
|
||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
@@ -285,8 +285,8 @@ github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1T
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
|
||||
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||
github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM=
|
||||
github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
|
||||
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
||||
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-co-op/gocron/v2 v2.21.2 h1:bD8/YwkojYHgXFr3iEulL148KBdTbKVxUZzFKpXcdbY=
|
||||
@@ -305,8 +305,6 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00=
|
||||
github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-ldap/ldap/v3 v3.4.13 h1:+x1nG9h+MZN7h/lUi5Q3UZ0fJ1GyDQYbPvbuH38baDQ=
|
||||
@@ -330,10 +328,10 @@ github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/go-webauthn/webauthn v0.17.3 h1:XHZ0TXV7k8vChcE4TFgPitOPJ5cb7h1dpAeFDS0cjCo=
|
||||
github.com/go-webauthn/webauthn v0.17.3/go.mod h1:PlkMgmuL9McCT7dvgBj/Sz/fgs3V6ZID6/KnFkEcPvQ=
|
||||
github.com/go-webauthn/x v0.2.5 h1:wEVTfU04XFyPTXGQbKOQwMKhcDWfDAkdsDDBsDaG9yY=
|
||||
github.com/go-webauthn/x v0.2.5/go.mod h1:Qna/yJz9rV6lRzwl5BfYbmTJpVGxcBIds3gJtw2tlGg=
|
||||
github.com/go-webauthn/webauthn v0.17.4 h1:KFTSz3R2RYDiUn/0cDi3XTJgFenSG74eKTTHlqWhlxk=
|
||||
github.com/go-webauthn/webauthn v0.17.4/go.mod h1:pZk63EE/BdztlmyS4Yc+9H5g4a8blNlbtGmdHQHbZX8=
|
||||
github.com/go-webauthn/x v0.2.6 h1:TEyDuQAIiEgYpx60nKiBJIX/5nSUC8LxNbH+uf5U9uk=
|
||||
github.com/go-webauthn/x v0.2.6/go.mod h1:45bA7YEqyQhRcQJ/TiBb46Ww8yqHBGvgEhQ3WWF0aDo=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||
@@ -538,8 +536,8 @@ github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI
|
||||
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.1.0 h1:QEt5IStDpxgGjEdtOgpiZ5QhmSl3ax7qy61vi2SwHO8=
|
||||
github.com/minio/minio-go/v7 v7.1.0/go.mod h1:Dm7WS1AgLmBa0NcQD6SeJnJf+K/EUW3GR7Ks6olB3OA=
|
||||
github.com/minio/minio-go/v7 v7.2.0 h1:RCJM0R1XOsRs+A3x3UCaf3ZYbByDaLjFeAi+YCQEPhs=
|
||||
github.com/minio/minio-go/v7 v7.2.0/go.mod h1:EU9hENAStx/xXduNdrGO5e4X5vk19NtgB+RIPjZO8o0=
|
||||
github.com/minio/minlz v1.1.0 h1:rUOGu3EP4EqJC5k3qCsIwEnZiJULKqtRyDdqbhlvMmQ=
|
||||
github.com/minio/minlz v1.1.0/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
@@ -569,10 +567,10 @@ github.com/nwaples/rardecode/v2 v2.2.2/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsR
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/oasdiff/yaml v0.0.9 h1:zQOvd2UKoozsSsAknnWoDJlSK4lC0mpmjfDsfqNwX48=
|
||||
github.com/oasdiff/yaml v0.0.9/go.mod h1:8lvhgJG4xiKPj3HN5lDow4jZHPlx1i7dIwzkdAo6oAM=
|
||||
github.com/oasdiff/yaml3 v0.0.12 h1:75urAtPeDg2/iDEWwzNrLOWxI9N/dCh81nTTJtokt2M=
|
||||
github.com/oasdiff/yaml3 v0.0.12/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
|
||||
github.com/oasdiff/yaml v0.1.0 h1:0bqZjfKc/8S9urj4JuwepX41WX9EoA6ifhU3SV06cXg=
|
||||
github.com/oasdiff/yaml v0.1.0/go.mod h1:kOlRmMdL2X3vucLCEQO5u61SU22RysnfXvcttrZA1O0=
|
||||
github.com/oasdiff/yaml3 v0.0.13 h1:06svmvOHOVBqF81+sY2EUScvUI/iS/vl2VIeUUxZQwg=
|
||||
github.com/oasdiff/yaml3 v0.0.13/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
|
||||
github.com/olekukonko/errors v1.3.0 h1:teJvgLGUEqMzBUms+Dj3/3szNqCG/Jdw9iDbum8fR6U=
|
||||
@@ -750,8 +748,8 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||
gitlab.com/gitlab-org/api/client-go/v2 v2.30.0 h1:guXC+uOA325P1FIwUVesPoDcnogHnbPrhV8c+wMHZPE=
|
||||
gitlab.com/gitlab-org/api/client-go/v2 v2.30.0/go.mod h1:0upZTKi9YMMvhavTlKJ3YgQUZE/GJkcLmXT/+6knTXU=
|
||||
gitlab.com/gitlab-org/api/client-go/v2 v2.34.0 h1:3Gv+41azLlj97fUkhkaTCnIOm45mHDwLlAcTDEwHE2s=
|
||||
gitlab.com/gitlab-org/api/client-go/v2 v2.34.0/go.mod h1:T+hA9p13Fxyh4FkVbcEy36HlAGs37QBCifhh7Zt4+dg=
|
||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
|
||||
@@ -64,8 +64,8 @@ func GetAssigneeIDsByIssue(ctx context.Context, issueID int64) ([]int64, error)
|
||||
}
|
||||
|
||||
// IsUserAssignedToIssue returns true when the user is assigned to the issue
|
||||
func IsUserAssignedToIssue(ctx context.Context, issue *Issue, user *user_model.User) (isAssigned bool, err error) {
|
||||
return db.Exist[IssueAssignees](ctx, builder.Eq{"assignee_id": user.ID, "issue_id": issue.ID})
|
||||
func IsUserAssignedToIssue(ctx context.Context, issue *Issue, userID int64) (isAssigned bool, err error) {
|
||||
return db.Exist[IssueAssignees](ctx, builder.Eq{"assignee_id": userID, "issue_id": issue.ID})
|
||||
}
|
||||
|
||||
type AssignedIssuesOptions struct {
|
||||
@@ -170,7 +170,7 @@ func toggleUserAssignee(ctx context.Context, issue *Issue, assigneeID int64) (re
|
||||
}
|
||||
|
||||
// MakeIDsFromAPIAssigneesToAdd returns an array with all assignee IDs
|
||||
func MakeIDsFromAPIAssigneesToAdd(ctx context.Context, oneAssignee string, multipleAssignees []string) (assigneeIDs []int64, err error) {
|
||||
func MakeIDsFromAPIAssigneesToAdd(ctx context.Context, oneAssignee string, multipleAssignees []string) ([]int64, error) {
|
||||
var requestAssignees []string
|
||||
|
||||
// Keeping the old assigning method for compatibility reasons
|
||||
@@ -184,7 +184,5 @@ func MakeIDsFromAPIAssigneesToAdd(ctx context.Context, oneAssignee string, multi
|
||||
}
|
||||
|
||||
// Get the IDs of all assignees
|
||||
assigneeIDs, err = user_model.GetUserIDsByNames(ctx, requestAssignees, false)
|
||||
|
||||
return assigneeIDs, err
|
||||
return user_model.GetUserIDsByNames(ctx, requestAssignees, false)
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ func TestUpdateAssignee(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check if he got removed
|
||||
isAssigned, err := issues_model.IsUserAssignedToIssue(t.Context(), issue, user1)
|
||||
isAssigned, err := issues_model.IsUserAssignedToIssue(t.Context(), issue, user1.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, isAssigned)
|
||||
|
||||
@@ -56,12 +56,12 @@ func TestUpdateAssignee(t *testing.T) {
|
||||
}
|
||||
|
||||
// Check if the user is assigned
|
||||
isAssigned, err = issues_model.IsUserAssignedToIssue(t.Context(), issue, user2)
|
||||
isAssigned, err = issues_model.IsUserAssignedToIssue(t.Context(), issue, user2.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, isAssigned)
|
||||
|
||||
// This user should not be assigned
|
||||
isAssigned, err = issues_model.IsUserAssignedToIssue(t.Context(), issue, &user_model.User{ID: 4})
|
||||
isAssigned, err = issues_model.IsUserAssignedToIssue(t.Context(), issue, 4)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, isAssigned)
|
||||
}
|
||||
|
||||
+28
-31
@@ -19,7 +19,6 @@ import (
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// ErrOrgNotExist represents a "OrgNotExist" kind of error.
|
||||
@@ -181,45 +180,43 @@ func (org *Organization) HomeLink() string {
|
||||
// FindOrgMembersOpts represents find org members conditions
|
||||
type FindOrgMembersOpts struct {
|
||||
db.ListOptions
|
||||
Doer *user_model.User
|
||||
IsDoerMember bool
|
||||
OrgID int64
|
||||
Keyword string
|
||||
SearchByEmail bool
|
||||
Doer *user_model.User
|
||||
IsDoerMember bool
|
||||
OrgID int64
|
||||
Keyword string
|
||||
}
|
||||
|
||||
func (opts FindOrgMembersOpts) PublicOnly() bool {
|
||||
return opts.Doer == nil || !(opts.IsDoerMember || opts.Doer.IsAdmin)
|
||||
}
|
||||
|
||||
func (opts FindOrgMembersOpts) applyKeywordFilter(sess *xorm.Session) (*xorm.Session, bool) {
|
||||
// applyKeywordFilter adds keyword search conditions to session
|
||||
func (opts FindOrgMembersOpts) applyKeywordFilter(sess db.Session) bool {
|
||||
if opts.Keyword == "" {
|
||||
return sess, false
|
||||
return false
|
||||
}
|
||||
|
||||
lowerKeyword := strings.ToLower(opts.Keyword)
|
||||
keywordCond := builder.Or(
|
||||
builder.Like{"`user`.lower_name", lowerKeyword},
|
||||
builder.Like{"LOWER(`user`.full_name)", lowerKeyword},
|
||||
db.BuildCaseInsensitiveLike("`user`.lower_name", opts.Keyword),
|
||||
db.BuildCaseInsensitiveLike("`user`.full_name", opts.Keyword),
|
||||
)
|
||||
if opts.SearchByEmail {
|
||||
var emailCond builder.Cond = builder.Like{"LOWER(`user`.email)", lowerKeyword}
|
||||
switch {
|
||||
case opts.Doer == nil:
|
||||
emailCond = emailCond.And(builder.Eq{"`user`.keep_email_private": false})
|
||||
case !opts.Doer.IsAdmin:
|
||||
emailCond = emailCond.And(
|
||||
builder.Or(
|
||||
builder.Eq{"`user`.keep_email_private": false},
|
||||
builder.Eq{"`user`.id": opts.Doer.ID},
|
||||
),
|
||||
)
|
||||
}
|
||||
keywordCond = keywordCond.Or(emailCond)
|
||||
}
|
||||
|
||||
sess = sess.Join("INNER", "`user`", "org_user.uid = `user`.id").And(keywordCond)
|
||||
return sess, true
|
||||
emailCond := db.BuildCaseInsensitiveLike("`user`.email", opts.Keyword)
|
||||
switch {
|
||||
case opts.Doer == nil:
|
||||
emailCond = emailCond.And(builder.Eq{"`user`.keep_email_private": false})
|
||||
case !opts.Doer.IsAdmin:
|
||||
emailCond = emailCond.And(
|
||||
builder.Or(
|
||||
builder.Eq{"`user`.keep_email_private": false},
|
||||
builder.Eq{"`user`.id": opts.Doer.ID},
|
||||
),
|
||||
)
|
||||
}
|
||||
keywordCond = keywordCond.Or(emailCond)
|
||||
|
||||
_ = sess.Join("INNER", "`user`", "org_user.uid = `user`.id").And(keywordCond)
|
||||
return true
|
||||
}
|
||||
|
||||
// applyTeamMatesOnlyFilter make sure restricted users only see public team members and there own team mates
|
||||
@@ -245,7 +242,7 @@ func CountOrgMembers(ctx context.Context, opts *FindOrgMembersOpts) (int64, erro
|
||||
} else {
|
||||
opts.applyTeamMatesOnlyFilter(sess)
|
||||
}
|
||||
sess, _ = opts.applyKeywordFilter(sess)
|
||||
_ = opts.applyKeywordFilter(sess)
|
||||
|
||||
return sess.Count(new(OrgUser))
|
||||
}
|
||||
@@ -494,8 +491,8 @@ func GetOrgUsersByOrgID(ctx context.Context, opts *FindOrgMembersOpts) ([]*OrgUs
|
||||
} else {
|
||||
opts.applyTeamMatesOnlyFilter(sess)
|
||||
}
|
||||
if keywordSess, hasKeyword := opts.applyKeywordFilter(sess); hasKeyword {
|
||||
sess = keywordSess.Select("org_user.*")
|
||||
if opts.applyKeywordFilter(sess) {
|
||||
sess = sess.Select("org_user.*")
|
||||
}
|
||||
|
||||
sess = sess.OrderBy("org_user.uid ASC")
|
||||
|
||||
@@ -302,43 +302,39 @@ func TestOrgMembersSearch(t *testing.T) {
|
||||
{
|
||||
name: "match by username",
|
||||
opts: &organization.FindOrgMembersOpts{
|
||||
OrgID: 3,
|
||||
Doer: member,
|
||||
IsDoerMember: true,
|
||||
Keyword: "user4",
|
||||
SearchByEmail: true,
|
||||
OrgID: 3,
|
||||
Doer: member,
|
||||
IsDoerMember: true,
|
||||
Keyword: "user4",
|
||||
},
|
||||
expectedUIDs: []int64{4},
|
||||
},
|
||||
{
|
||||
name: "match by full name",
|
||||
opts: &organization.FindOrgMembersOpts{
|
||||
OrgID: 3,
|
||||
Doer: member,
|
||||
IsDoerMember: true,
|
||||
Keyword: "user27",
|
||||
SearchByEmail: true,
|
||||
OrgID: 3,
|
||||
Doer: member,
|
||||
IsDoerMember: true,
|
||||
Keyword: "user27",
|
||||
},
|
||||
expectedUIDs: []int64{28},
|
||||
},
|
||||
{
|
||||
name: "private email hidden",
|
||||
opts: &organization.FindOrgMembersOpts{
|
||||
OrgID: 3,
|
||||
Doer: member,
|
||||
IsDoerMember: true,
|
||||
Keyword: "user2@example.com",
|
||||
SearchByEmail: true,
|
||||
OrgID: 3,
|
||||
Doer: member,
|
||||
IsDoerMember: true,
|
||||
Keyword: "user2@example.com",
|
||||
},
|
||||
expectedUIDs: []int64{},
|
||||
},
|
||||
{
|
||||
name: "admin can search private email",
|
||||
opts: &organization.FindOrgMembersOpts{
|
||||
OrgID: 3,
|
||||
Doer: admin,
|
||||
Keyword: "user2@example.com",
|
||||
SearchByEmail: true,
|
||||
OrgID: 3,
|
||||
Doer: admin,
|
||||
Keyword: "user2@example.com",
|
||||
},
|
||||
expectedUIDs: []int64{2},
|
||||
},
|
||||
|
||||
@@ -567,9 +567,9 @@ func HasAccessUnit(ctx context.Context, user *user_model.User, repo *repo_model.
|
||||
|
||||
// CanBeAssigned return true if user can be assigned to issue or pull requests in repo
|
||||
// Currently any write access (code, issues or pr's) is assignable, to match assignee list in user interface.
|
||||
func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.Repository, _ bool) (bool, error) {
|
||||
func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.Repository) (bool, error) {
|
||||
if user.IsOrganization() {
|
||||
return false, fmt.Errorf("organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID)
|
||||
return false, util.NewInvalidArgumentErrorf("organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID)
|
||||
}
|
||||
perm, err := GetIndividualUserRepoPermission(ctx, repo, user)
|
||||
if err != nil {
|
||||
|
||||
@@ -125,6 +125,11 @@ type EditIssueOption struct {
|
||||
ContentVersion *int `json:"content_version"`
|
||||
}
|
||||
|
||||
// IssueAssigneesOption options for adding/removing issue assignees
|
||||
type IssueAssigneesOption struct {
|
||||
Assignees []string `json:"assignees"`
|
||||
}
|
||||
|
||||
// EditDeadlineOption options for creating a deadline
|
||||
type EditDeadlineOption struct {
|
||||
// required:true
|
||||
|
||||
+1
-1
@@ -106,7 +106,7 @@
|
||||
"eslint-plugin-vue-scoped-css": "3.1.1",
|
||||
"eslint-plugin-wc": "3.1.0",
|
||||
"globals": "17.6.0",
|
||||
"happy-dom": "20.9.0",
|
||||
"happy-dom": "20.10.1",
|
||||
"jiti": "2.7.0",
|
||||
"markdownlint-cli": "0.48.0",
|
||||
"material-icon-theme": "5.35.0",
|
||||
|
||||
Generated
+64
-29
@@ -257,7 +257,7 @@ importers:
|
||||
version: 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)
|
||||
'@vitest/eslint-plugin':
|
||||
specifier: 1.6.19
|
||||
version: 1.6.19(@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.8(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)))
|
||||
version: 1.6.19(@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.8(@types/node@25.9.1)(happy-dom@20.10.1)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)))
|
||||
eslint:
|
||||
specifier: 10.4.1
|
||||
version: 10.4.1(jiti@2.7.0)
|
||||
@@ -301,8 +301,8 @@ importers:
|
||||
specifier: 17.6.0
|
||||
version: 17.6.0
|
||||
happy-dom:
|
||||
specifier: 20.9.0
|
||||
version: 20.9.0
|
||||
specifier: 20.10.1
|
||||
version: 20.10.1
|
||||
jiti:
|
||||
specifier: 2.7.0
|
||||
version: 2.7.0
|
||||
@@ -347,7 +347,7 @@ importers:
|
||||
version: 17.17.3
|
||||
vitest:
|
||||
specifier: 4.1.8
|
||||
version: 4.1.8(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))
|
||||
version: 4.1.8(@types/node@25.9.1)(happy-dom@20.10.1)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))
|
||||
vue-tsc:
|
||||
specifier: 3.3.3
|
||||
version: 3.3.3(typescript@6.0.3)
|
||||
@@ -1879,6 +1879,10 @@ packages:
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
|
||||
buffer-image-size@0.6.4:
|
||||
resolution: {integrity: sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==}
|
||||
engines: {node: '>=4.0'}
|
||||
|
||||
buffer@5.7.1:
|
||||
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
||||
|
||||
@@ -3006,8 +3010,8 @@ packages:
|
||||
resolution: {integrity: sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
|
||||
happy-dom@20.9.0:
|
||||
resolution: {integrity: sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ==}
|
||||
happy-dom@20.10.1:
|
||||
resolution: {integrity: sha512-awPoqPjx8CgjapJllyDlgzgVHjBExcitKK5ZJkxwhQJyQpHFkyS2bEcqCm7IeW20cQvuCI0cz2Ifq79CJKqtiw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
har-schema@2.0.0:
|
||||
@@ -6125,22 +6129,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@typescript-eslint/parser': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)
|
||||
'@typescript-eslint/scope-manager': 8.60.1
|
||||
'@typescript-eslint/type-utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3)
|
||||
'@typescript-eslint/visitor-keys': 8.60.1
|
||||
eslint: 10.4.1(jiti@2.7.0)
|
||||
ignore: 7.0.5
|
||||
natural-compare: 1.4.0
|
||||
ts-api-utils: 2.5.0(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
@@ -6376,7 +6364,7 @@ snapshots:
|
||||
vite: 8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)
|
||||
vue: 3.5.35(typescript@6.0.3)
|
||||
|
||||
'@vitest/eslint-plugin@1.6.19(@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.8(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)))':
|
||||
'@vitest/eslint-plugin@1.6.19(@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.8(@types/node@25.9.1)(happy-dom@20.10.1)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)))':
|
||||
dependencies:
|
||||
'@typescript-eslint/scope-manager': 8.60.1
|
||||
'@typescript-eslint/utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)
|
||||
@@ -6384,7 +6372,7 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/eslint-plugin': 8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)
|
||||
typescript: 6.0.3
|
||||
vitest: 4.1.8(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))
|
||||
vitest: 4.1.8(@types/node@25.9.1)(happy-dom@20.10.1)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -6710,6 +6698,10 @@ snapshots:
|
||||
node-releases: 2.0.46
|
||||
update-browserslist-db: 1.2.3(browserslist@4.28.2)
|
||||
|
||||
buffer-image-size@0.6.4:
|
||||
dependencies:
|
||||
'@types/node': 25.9.1
|
||||
|
||||
buffer@5.7.1:
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
@@ -7459,6 +7451,17 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.13.0(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3)
|
||||
eslint: 10.4.1(jiti@2.7.0)
|
||||
eslint-import-resolver-node: 0.3.10
|
||||
eslint-import-resolver-typescript: 4.4.5(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.1(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.4.1(jiti@2.7.0))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.13.0(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
@@ -7469,6 +7472,7 @@ snapshots:
|
||||
eslint-import-resolver-typescript: 4.4.5(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.1(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.4.1(jiti@2.7.0))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
optional: true
|
||||
|
||||
eslint-plugin-array-func@5.1.1(eslint@10.4.1(jiti@2.7.0)):
|
||||
dependencies:
|
||||
@@ -7503,7 +7507,7 @@ snapshots:
|
||||
'@eslint/eslintrc': 3.3.5
|
||||
'@eslint/js': 9.39.4
|
||||
'@github/browserslist-config': 1.0.0
|
||||
'@typescript-eslint/eslint-plugin': 8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3)
|
||||
'@typescript-eslint/eslint-plugin': 8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3)
|
||||
aria-query: 5.3.2
|
||||
eslint: 10.4.1(jiti@2.7.0)
|
||||
@@ -7512,7 +7516,7 @@ snapshots:
|
||||
eslint-plugin-eslint-comments: 3.2.0(eslint@10.4.1(jiti@2.7.0))
|
||||
eslint-plugin-filenames: 1.3.2(eslint@10.4.1(jiti@2.7.0))
|
||||
eslint-plugin-i18n-text: 1.0.1(eslint@10.4.1(jiti@2.7.0))
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0))
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0))
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@10.4.1(jiti@2.7.0))
|
||||
eslint-plugin-no-only-tests: 3.4.0
|
||||
eslint-plugin-prettier: 5.5.6(eslint-config-prettier@10.1.8(eslint@10.4.1(jiti@2.7.0)))(eslint@10.4.1(jiti@2.7.0))(prettier@3.8.3)
|
||||
@@ -7552,6 +7556,35 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.9
|
||||
array.prototype.findlastindex: 1.2.6
|
||||
array.prototype.flat: 1.3.3
|
||||
array.prototype.flatmap: 1.3.3
|
||||
debug: 3.2.7
|
||||
doctrine: 2.1.0
|
||||
eslint: 10.4.1(jiti@2.7.0)
|
||||
eslint-import-resolver-node: 0.3.10
|
||||
eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0))
|
||||
hasown: 2.0.4
|
||||
is-core-module: 2.16.2
|
||||
is-glob: 4.0.3
|
||||
minimatch: 3.1.5
|
||||
object.fromentries: 2.0.8
|
||||
object.groupby: 1.0.3
|
||||
object.values: 1.2.1
|
||||
semver: 6.3.1
|
||||
string.prototype.trimend: 1.0.9
|
||||
tsconfig-paths: 3.15.0
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3)
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-typescript
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
@@ -7580,6 +7613,7 @@ snapshots:
|
||||
- eslint-import-resolver-typescript
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
optional: true
|
||||
|
||||
eslint-plugin-jsx-a11y@6.10.2(eslint@10.4.1(jiti@2.7.0)):
|
||||
dependencies:
|
||||
@@ -8013,11 +8047,12 @@ snapshots:
|
||||
|
||||
hammerjs@2.0.8: {}
|
||||
|
||||
happy-dom@20.9.0:
|
||||
happy-dom@20.10.1:
|
||||
dependencies:
|
||||
'@types/node': 25.9.1
|
||||
'@types/whatwg-mimetype': 3.0.2
|
||||
'@types/ws': 8.18.1
|
||||
buffer-image-size: 0.6.4
|
||||
entities: 7.0.1
|
||||
whatwg-mimetype: 3.0.0
|
||||
ws: 8.21.0
|
||||
@@ -10001,7 +10036,7 @@ snapshots:
|
||||
fsevents: 2.3.3
|
||||
jiti: 2.7.0
|
||||
|
||||
vitest@4.1.8(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)):
|
||||
vitest@4.1.8(@types/node@25.9.1)(happy-dom@20.10.1)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)):
|
||||
dependencies:
|
||||
'@vitest/expect': 4.1.8
|
||||
'@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))
|
||||
@@ -10025,7 +10060,7 @@ snapshots:
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/node': 25.9.1
|
||||
happy-dom: 20.9.0
|
||||
happy-dom: 20.10.1
|
||||
jsdom: 20.0.3
|
||||
transitivePeerDependencies:
|
||||
- msw
|
||||
|
||||
@@ -1226,6 +1226,7 @@ func Routes() *web.Router {
|
||||
})
|
||||
}, reqToken())
|
||||
m.Get("/assignees", reqToken(), reqAnyRepoReader(), repo.GetAssignees)
|
||||
m.Get("/assignees/{assignee}", reqToken(), reqAnyRepoReader(), repo.CheckRepoIssueAssignee)
|
||||
m.Get("/reviewers", reqToken(), reqAnyRepoReader(), repo.GetReviewers)
|
||||
m.Group("/teams", func() {
|
||||
m.Get("", reqAnyRepoReader(), repo.ListTeams)
|
||||
@@ -1517,6 +1518,10 @@ func Routes() *web.Router {
|
||||
m.Combo("").Get(repo.GetIssue).
|
||||
Patch(reqToken(), bind(api.EditIssueOption{}), repo.EditIssue).
|
||||
Delete(reqToken(), reqAdmin(), context.ReferencesGitRepo(), repo.DeleteIssue)
|
||||
m.Combo("/assignees").
|
||||
Post(reqToken(), mustNotBeArchived, bind(api.IssueAssigneesOption{}), repo.AddIssueAssignees).
|
||||
Delete(reqToken(), mustNotBeArchived, bind(api.IssueAssigneesOption{}), repo.DeleteIssueAssignees)
|
||||
m.Get("/assignees/{assignee}", repo.CheckIssueAssignee)
|
||||
m.Group("/comments", func() {
|
||||
m.Combo("").Get(repo.ListIssueComments).
|
||||
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
|
||||
|
||||
@@ -367,5 +367,42 @@ func GetAssignees(ctx *context.APIContext) {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.ToUsers(ctx, ctx.Doer, assignees))
|
||||
}
|
||||
|
||||
// CheckRepoIssueAssignee check if a user can be assigned to issues in a repository
|
||||
func CheckRepoIssueAssignee(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/assignees/{assignee} repository repoCheckAssignee
|
||||
// ---
|
||||
// summary: Check if a user can be assigned to issues in a repository
|
||||
// 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: assignee
|
||||
// in: path
|
||||
// description: username of the user to check for being an assignee
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
if checkAssignableUser(ctx, ctx.PathParam("assignee"), ctx.Repo.Repository) {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -670,7 +670,7 @@ func CreateIssue(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
valid, err := access_model.CanBeAssigned(ctx, assignee, ctx.Repo.Repository, false)
|
||||
valid, err := access_model.CanBeAssigned(ctx, assignee, ctx.Repo.Repository)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
issues_model "gitea.dev/models/issues"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
user_model "gitea.dev/models/user"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
issue_service "gitea.dev/services/issue"
|
||||
)
|
||||
|
||||
// AddIssueAssignees add assignees to an issue
|
||||
func AddIssueAssignees(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/issues/{index}/assignees issue issueAddAssignees
|
||||
// ---
|
||||
// summary: Add assignees to an issue
|
||||
// consumes:
|
||||
// - application/json
|
||||
// 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: index
|
||||
// in: path
|
||||
// description: index of the issue
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// required: true
|
||||
// schema:
|
||||
// "$ref": "#/definitions/IssueAssigneesOption"
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/Issue"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
opts := web.GetForm(ctx).(*api.IssueAssigneesOption)
|
||||
updateIssueAssignees(ctx, *opts, true)
|
||||
}
|
||||
|
||||
// DeleteIssueAssignees remove assignees from an issue
|
||||
func DeleteIssueAssignees(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/assignees issue issueRemoveAssignees
|
||||
// ---
|
||||
// summary: Remove assignees from an issue
|
||||
// consumes:
|
||||
// - application/json
|
||||
// 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: index
|
||||
// in: path
|
||||
// description: index of the issue
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// required: true
|
||||
// schema:
|
||||
// "$ref": "#/definitions/IssueAssigneesOption"
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Issue"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
opts := web.GetForm(ctx).(*api.IssueAssigneesOption)
|
||||
updateIssueAssignees(ctx, *opts, false)
|
||||
}
|
||||
|
||||
// CheckIssueAssignee check if a user can be assigned to an issue
|
||||
func CheckIssueAssignee(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/issues/{index}/assignees/{assignee} issue issueCheckAssignee
|
||||
// ---
|
||||
// summary: Check if a user can be assigned to an issue
|
||||
// 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: index
|
||||
// in: path
|
||||
// description: index of the issue
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// - name: assignee
|
||||
// in: path
|
||||
// description: username of the user to check for being an assignee
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.Permission.CanReadIssuesOrPulls(issue.IsPull) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if checkAssignableUser(ctx, ctx.PathParam("assignee"), ctx.Repo.Repository) {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
// checkAssignableUser resolves assigneeName and verifies the user can be assigned to issues in repo.
|
||||
// Returns true only when the user resolves AND is assignable; the caller is responsible for writing the 204.
|
||||
// On any failure it writes the appropriate API response and returns false.
|
||||
func checkAssignableUser(ctx *context.APIContext, assigneeName string, repo *repo_model.Repository) bool {
|
||||
assignee, err := user_model.GetUserByName(ctx, assigneeName)
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return false
|
||||
}
|
||||
|
||||
canAssign, err := access_model.CanBeAssigned(ctx, assignee, repo)
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return false
|
||||
}
|
||||
|
||||
if !canAssign {
|
||||
ctx.APIErrorNotFound()
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func updateIssueAssignees(ctx *context.APIContext, opts api.IssueAssigneesOption, isAdd bool) {
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||
ctx.Status(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if err := issue.LoadAttributes(ctx); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
assigneeIDs, err := user_model.GetUserIDsByNames(ctx, opts.Assignees, false)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
if isAdd {
|
||||
err = issue_service.AddAssignees(ctx, issue, ctx.Doer, assigneeIDs)
|
||||
} else {
|
||||
err = issue_service.RemoveAssignees(ctx, issue, ctx.Doer, assigneeIDs)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
issue, err = issues_model.GetIssueByID(ctx, issue.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
status := http.StatusOK
|
||||
if isAdd {
|
||||
status = http.StatusCreated
|
||||
}
|
||||
ctx.JSON(status, convert.ToAPIIssue(ctx, ctx.Doer, issue))
|
||||
}
|
||||
@@ -545,7 +545,7 @@ func CreatePullRequest(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
valid, err := access_model.CanBeAssigned(ctx, assignee, repo, true)
|
||||
valid, err := access_model.CanBeAssigned(ctx, assignee, repo)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
|
||||
@@ -36,6 +36,8 @@ type swaggerParameterBodies struct {
|
||||
EditIssueOption api.EditIssueOption
|
||||
// in:body
|
||||
EditDeadlineOption api.EditDeadlineOption
|
||||
// in:body
|
||||
IssueAssigneesOption api.IssueAssigneesOption
|
||||
|
||||
// in:body
|
||||
CreateIssueCommentOption api.CreateIssueCommentOption
|
||||
|
||||
@@ -6,6 +6,7 @@ package private
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@@ -18,7 +19,6 @@ import (
|
||||
"gitea.dev/services/context"
|
||||
|
||||
"gitea.com/go-chi/binding"
|
||||
chi_middleware "github.com/go-chi/chi/v5/middleware"
|
||||
)
|
||||
|
||||
func authInternal(next http.Handler) http.Handler {
|
||||
@@ -50,6 +50,18 @@ func bind[T any](_ T) any {
|
||||
}
|
||||
}
|
||||
|
||||
// setRealIP sets RemoteAddr from the trusted X-Real-IP header set by the internal API
|
||||
// client (see modules/private.NewInternalRequest); the internal API is gated by InternalToken.
|
||||
// It replaces chi's deprecated middleware.RealIP, which is unsafe on public-facing endpoints.
|
||||
func setRealIP(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
if ip := req.Header.Get("X-Real-IP"); net.ParseIP(ip) != nil {
|
||||
req.RemoteAddr = ip
|
||||
}
|
||||
next.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
|
||||
// Routes registers all internal APIs routes to web application.
|
||||
// These APIs will be invoked by internal commands for example `gitea serv` and etc.
|
||||
func Routes() *web.Router {
|
||||
@@ -58,7 +70,7 @@ func Routes() *web.Router {
|
||||
r.AfterRouting(authInternal)
|
||||
// Log the real ip address of the request from SSH is really helpful for diagnosing sometimes.
|
||||
// Since internal API will be sent only from Gitea sub commands and it's under control (checked by InternalToken), we can trust the headers.
|
||||
r.AfterRouting(chi_middleware.RealIP)
|
||||
r.AfterRouting(setRealIP)
|
||||
|
||||
r.Get("/dummy", misc.DummyOK)
|
||||
r.Post("/ssh/authorized_keys", AuthorizedPublicKeyByContent)
|
||||
|
||||
@@ -35,16 +35,15 @@ func Members(ctx *context.Context) {
|
||||
ctx.Data["Keyword"] = keyword
|
||||
|
||||
opts := &organization.FindOrgMembersOpts{
|
||||
Doer: ctx.Doer,
|
||||
OrgID: org.ID,
|
||||
Keyword: keyword,
|
||||
SearchByEmail: true,
|
||||
Doer: ctx.Doer,
|
||||
OrgID: org.ID,
|
||||
Keyword: keyword,
|
||||
}
|
||||
|
||||
if ctx.Doer != nil {
|
||||
isMember, err := ctx.Org.Organization.IsOrgMember(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.HTTPError(http.StatusInternalServerError, "IsOrgMember")
|
||||
ctx.ServerError("IsOrgMember", err)
|
||||
return
|
||||
}
|
||||
opts.IsDoerMember = isMember
|
||||
@@ -53,7 +52,7 @@ func Members(ctx *context.Context) {
|
||||
|
||||
total, err := organization.CountOrgMembers(ctx, opts)
|
||||
if err != nil {
|
||||
ctx.HTTPError(http.StatusInternalServerError, "CountOrgMembers")
|
||||
ctx.ServerError("CountOrgMembers", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -74,8 +73,6 @@ func Members(ctx *context.Context) {
|
||||
}
|
||||
ctx.Data["Page"] = pager
|
||||
ctx.Data["Members"] = members
|
||||
ctx.Data["MembersShown"] = len(members)
|
||||
ctx.Data["MembersTotal"] = total
|
||||
ctx.Data["MembersIsPublicMember"] = membersIsPublic
|
||||
ctx.Data["MembersIsUserOrgOwner"] = organization.IsUserOrgOwner(ctx, members, org.ID)
|
||||
ctx.Data["MembersTwoFaStatus"] = members.GetTwoFaStatus(ctx)
|
||||
|
||||
@@ -458,7 +458,7 @@ func UpdateIssueAssignee(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
valid, err := access_model.CanBeAssigned(ctx, assignee, issue.Repo, issue.IsPull)
|
||||
valid, err := access_model.CanBeAssigned(ctx, assignee, issue.Repo)
|
||||
if err != nil {
|
||||
ctx.ServerError("canBeAssigned", err)
|
||||
return
|
||||
|
||||
@@ -242,7 +242,7 @@ func (r *Repository) CanUseTimetracker(ctx context.Context, issue *issues_model.
|
||||
// Checking for following:
|
||||
// 1. Is timetracker enabled
|
||||
// 2. Is the user a contributor, admin, poster or assignee and do the repository policies require this?
|
||||
isAssigned, _ := issues_model.IsUserAssignedToIssue(ctx, issue, user)
|
||||
isAssigned, _ := issues_model.IsUserAssignedToIssue(ctx, issue, user.ID)
|
||||
return r.Repository.IsTimetrackerEnabled(ctx) && (!r.Repository.AllowOnlyContributorsToTrackTime(ctx) ||
|
||||
r.Permission.CanWriteIssuesOrPulls(issue.IsPull) || issue.IsPoster(user.ID) || isAssigned)
|
||||
}
|
||||
|
||||
+182
-40
@@ -6,6 +6,7 @@ package issue
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
@@ -14,8 +15,7 @@ import (
|
||||
notify_service "gitea.dev/services/notify"
|
||||
)
|
||||
|
||||
// DeleteNotPassedAssignee deletes all assignees who aren't passed via the "assignees" array
|
||||
func DeleteNotPassedAssignee(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assignees []*user_model.User) (err error) {
|
||||
func toBeRemovedAssignees(issue *issues_model.Issue, assignees []*user_model.User) (toBeRemovedAssignees []*user_model.User) {
|
||||
var found bool
|
||||
oriAssignees := make([]*user_model.User, len(issue.Assignees))
|
||||
_ = copy(oriAssignees, issue.Assignees)
|
||||
@@ -31,28 +31,54 @@ func DeleteNotPassedAssignee(ctx context.Context, issue *issues_model.Issue, doe
|
||||
|
||||
if !found {
|
||||
// This function also does comments and hooks, which is why we call it separately instead of directly removing the assignees here
|
||||
if _, _, err := ToggleAssigneeWithNotify(ctx, issue, doer, assignee.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
toBeRemovedAssignees = append(toBeRemovedAssignees, assignee)
|
||||
}
|
||||
}
|
||||
return toBeRemovedAssignees
|
||||
}
|
||||
|
||||
// DeleteNotPassedAssignee deletes all assignees who aren't passed via the "assignees" array
|
||||
func DeleteNotPassedAssignee(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assignees []*user_model.User) (err error) {
|
||||
toBeRemoved := toBeRemovedAssignees(issue, assignees)
|
||||
|
||||
for _, assignee := range toBeRemoved {
|
||||
// This function also does comments and hooks, which is why we call it separately instead of directly removing the assignees here
|
||||
removed, comment, err := ToggleAssignee(ctx, issue, doer, assignee)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if removed {
|
||||
notify_service.IssueChangeAssignee(ctx, doer, issue, assignee, true, comment)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToggleAssigneeWithNoNotify changes a user between assigned and not assigned for this issue, and make issue comment for it.
|
||||
func ToggleAssigneeWithNotify(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *issues_model.Comment, err error) {
|
||||
removed, comment, err = issues_model.ToggleIssueAssignee(ctx, issue, doer, assigneeID)
|
||||
// ToggleAssignee changes a user between assigned and not assigned for this issue, and make issue comment for it.
|
||||
func ToggleAssignee(ctx context.Context, issue *issues_model.Issue, doer, assignee *user_model.User) (removed bool, comment *issues_model.Comment, err error) {
|
||||
removed, comment, err = issues_model.ToggleIssueAssignee(ctx, issue, doer, assignee.ID)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
issue.AssigneeID = assignee.ID
|
||||
issue.Assignee = assignee
|
||||
|
||||
return removed, comment, nil
|
||||
}
|
||||
|
||||
// ToggleAssignee changes a user between assigned and not assigned for this issue, and make issue comment for it.
|
||||
func ToggleAssigneeWithNotify(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *issues_model.Comment, err error) {
|
||||
assignee, err := user_model.GetUserByID(ctx, assigneeID)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
issue.AssigneeID = assigneeID
|
||||
issue.Assignee = assignee
|
||||
|
||||
removed, comment, err = ToggleAssignee(ctx, issue, doer, assignee)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
notify_service.IssueChangeAssignee(ctx, doer, issue, assignee, removed, comment)
|
||||
|
||||
@@ -81,43 +107,85 @@ func UpdateAssignees(ctx context.Context, issue *issues_model.Issue, oneAssignee
|
||||
return err
|
||||
}
|
||||
|
||||
if user_model.IsUserBlockedBy(ctx, doer, assignee.ID) {
|
||||
return user_model.ErrBlockedUser
|
||||
if err := validateAssignee(ctx, issue, doer, assignee); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
allNewAssignees = append(allNewAssignees, assignee)
|
||||
}
|
||||
|
||||
// Delete all old assignees not passed
|
||||
if err = DeleteNotPassedAssignee(ctx, issue, doer, allNewAssignees); err != nil {
|
||||
assigneeCommentMap := make(map[int64]*issues_model.Comment)
|
||||
assigneeRemovedCommentMap := make(map[int64]*issues_model.Comment)
|
||||
assigneeRemoved := make(map[int64]*user_model.User)
|
||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
// Delete all old assignees not passed.
|
||||
toBeRemoved := toBeRemovedAssignees(issue, allNewAssignees)
|
||||
|
||||
for _, assignee := range toBeRemoved {
|
||||
// This function also does comments and hooks, which is why we call it separately instead of directly removing the assignees here
|
||||
removed, comment, err := ToggleAssignee(ctx, issue, doer, assignee)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if removed {
|
||||
assigneeRemoved[assignee.ID] = assignee
|
||||
assigneeRemovedCommentMap[assignee.ID] = comment
|
||||
}
|
||||
}
|
||||
|
||||
// Add all new assignees.
|
||||
// Update the assignee. The function will check if the user exists, is already
|
||||
// assigned (which he shouldn't as we deleted all assignees before) and
|
||||
// has access to the repo.
|
||||
for _, assignee := range allNewAssignees {
|
||||
// Extra method to prevent double adding (which would result in removing).
|
||||
comment, err := AddAssigneeIfNotAssigned(ctx, issue, doer, assignee)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
assigneeCommentMap[assignee.ID] = comment
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add all new assignees
|
||||
// Update the assignee. The function will check if the user exists, is already
|
||||
// assigned (which he shouldn't as we deleted all assignees before) and
|
||||
// has access to the repo.
|
||||
for _, assignee := range assigneeRemoved {
|
||||
notify_service.IssueChangeAssignee(ctx, doer, issue, assignee, true, assigneeRemovedCommentMap[assignee.ID])
|
||||
}
|
||||
|
||||
for _, assignee := range allNewAssignees {
|
||||
// Extra method to prevent double adding (which would result in removing)
|
||||
_, err = AddAssigneeIfNotAssigned(ctx, issue, doer, assignee.ID, true)
|
||||
if err != nil {
|
||||
return err
|
||||
comment := assigneeCommentMap[assignee.ID]
|
||||
if comment != nil {
|
||||
notify_service.IssueChangeAssignee(ctx, doer, issue, assignee, false, comment)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateAssignee(ctx context.Context, issue *issues_model.Issue, doer, assignee *user_model.User) error {
|
||||
if user_model.IsUserBlockedBy(ctx, doer, assignee.ID) {
|
||||
return user_model.ErrBlockedUser
|
||||
}
|
||||
|
||||
valid, err := access_model.CanBeAssigned(ctx, assignee, issue.Repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !valid {
|
||||
return repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: assignee.ID, RepoName: issue.Repo.Name}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddAssigneeIfNotAssigned adds an assignee only if he isn't already assigned to the issue.
|
||||
// Also checks for access of assigned user
|
||||
func AddAssigneeIfNotAssigned(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64, notify bool) (comment *issues_model.Comment, err error) {
|
||||
assignee, err := user_model.GetUserByID(ctx, assigneeID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func AddAssigneeIfNotAssigned(ctx context.Context, issue *issues_model.Issue, doer, assignee *user_model.User) (comment *issues_model.Comment, err error) {
|
||||
// Check if the user is already assigned
|
||||
isAssigned, err := issues_model.IsUserAssignedToIssue(ctx, issue, assignee)
|
||||
isAssigned, err := issues_model.IsUserAssignedToIssue(ctx, issue, assignee.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -126,18 +194,92 @@ func AddAssigneeIfNotAssigned(ctx context.Context, issue *issues_model.Issue, do
|
||||
return nil, nil //nolint:nilnil // return nil because the user is already assigned
|
||||
}
|
||||
|
||||
valid, err := access_model.CanBeAssigned(ctx, assignee, issue.Repo, issue.IsPull)
|
||||
if err != nil {
|
||||
if err := validateAssignee(ctx, issue, doer, assignee); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !valid {
|
||||
return nil, repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: assigneeID, RepoName: issue.Repo.Name}
|
||||
}
|
||||
|
||||
if notify {
|
||||
_, comment, err = ToggleAssigneeWithNotify(ctx, issue, doer, assigneeID)
|
||||
return comment, err
|
||||
}
|
||||
_, comment, err = issues_model.ToggleIssueAssignee(ctx, issue, doer, assigneeID)
|
||||
_, comment, err = issues_model.ToggleIssueAssignee(ctx, issue, doer, assignee.ID)
|
||||
return comment, err
|
||||
}
|
||||
|
||||
// AddAssignees adds multiple assignees to an issue atomically.
|
||||
func AddAssignees(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeIDs []int64) error {
|
||||
assigneeCommentMap := make(map[int64]*issues_model.Comment)
|
||||
assignees := make(map[int64]*user_model.User)
|
||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
for _, assigneeID := range assigneeIDs {
|
||||
isAssigned, err := issues_model.IsUserAssignedToIssue(ctx, issue, assigneeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isAssigned {
|
||||
continue
|
||||
}
|
||||
|
||||
assignee, err := user_model.GetUserByID(ctx, assigneeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateAssignee(ctx, issue, doer, assignee); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
comment, err := AddAssigneeIfNotAssigned(ctx, issue, doer, assignee)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
assignees[assigneeID] = assignee
|
||||
assigneeCommentMap[assigneeID] = comment
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(assignees) > 0 {
|
||||
for assigneeID, assignee := range assignees {
|
||||
notify_service.IssueChangeAssignee(ctx, doer, issue, assignee, false, assigneeCommentMap[assigneeID])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveAssignees removes multiple assignees from an issue atomically.
|
||||
func RemoveAssignees(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeIDs []int64) error {
|
||||
assigneeCommentMap := make(map[int64]*issues_model.Comment)
|
||||
assignees := make(map[int64]*user_model.User)
|
||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
for _, assigneeID := range assigneeIDs {
|
||||
isAssigned, err := issues_model.IsUserAssignedToIssue(ctx, issue, assigneeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isAssigned {
|
||||
continue
|
||||
}
|
||||
removed, comment, err := issues_model.ToggleIssueAssignee(ctx, issue, doer, assigneeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if removed {
|
||||
assignee, err := user_model.GetUserByID(ctx, assigneeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
assignees[assigneeID] = assignee
|
||||
assigneeCommentMap[assigneeID] = comment
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(assignees) > 0 {
|
||||
for assigneeID, assignee := range assignees {
|
||||
notify_service.IssueChangeAssignee(ctx, doer, issue, assignee, true, assigneeCommentMap[assigneeID])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ package issue
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
"gitea.dev/models/unittest"
|
||||
user_model "gitea.dev/models/user"
|
||||
@@ -29,7 +30,7 @@ func TestDeleteNotPassedAssignee(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check if he got removed
|
||||
isAssigned, err := issues_model.IsUserAssignedToIssue(t.Context(), issue, user1)
|
||||
isAssigned, err := issues_model.IsUserAssignedToIssue(t.Context(), issue, user1.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, isAssigned)
|
||||
|
||||
@@ -44,3 +45,55 @@ func TestDeleteNotPassedAssignee(t *testing.T) {
|
||||
assert.Empty(t, issue.Assignees)
|
||||
assert.Empty(t, issue.Assignee)
|
||||
}
|
||||
|
||||
func TestAddAssigneeIfNotAssignedBlocked(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
issue, err := issues_model.GetIssueByID(t.Context(), 1)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, issue.LoadRepo(t.Context()))
|
||||
|
||||
doer, err := user_model.GetUserByID(t.Context(), 4)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assignee, err := user_model.GetUserByID(t.Context(), 2)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, db.Insert(t.Context(), &user_model.Blocking{
|
||||
BlockerID: assignee.ID,
|
||||
BlockeeID: doer.ID,
|
||||
}))
|
||||
|
||||
_, err = AddAssigneeIfNotAssigned(t.Context(), issue, doer, assignee)
|
||||
assert.ErrorIs(t, err, user_model.ErrBlockedUser)
|
||||
|
||||
isAssigned, err := issues_model.IsUserAssignedToIssue(t.Context(), issue, assignee.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, isAssigned)
|
||||
}
|
||||
|
||||
func TestAddAssigneesBlockedIsAtomic(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
issue, err := issues_model.GetIssueByID(t.Context(), 1)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, issue.LoadAttributes(t.Context()))
|
||||
|
||||
doer, err := user_model.GetUserByID(t.Context(), 2)
|
||||
assert.NoError(t, err)
|
||||
|
||||
blockedAssignee, err := user_model.GetUserByID(t.Context(), 40)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, db.Insert(t.Context(), &user_model.Blocking{
|
||||
BlockerID: blockedAssignee.ID,
|
||||
BlockeeID: doer.ID,
|
||||
}))
|
||||
|
||||
err = AddAssignees(t.Context(), issue, doer, []int64{doer.ID, blockedAssignee.ID})
|
||||
assert.ErrorIs(t, err, user_model.ErrBlockedUser)
|
||||
|
||||
assigneeIDs, err := issues_model.GetAssigneeIDsByIssue(t.Context(), issue.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t, []int64{1}, assigneeIDs)
|
||||
}
|
||||
|
||||
+17
-1
@@ -32,14 +32,24 @@ func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *issues_mo
|
||||
return user_model.ErrBlockedUser
|
||||
}
|
||||
|
||||
assigneeCommentMap := make(map[int64]*issues_model.Comment)
|
||||
assignees := make(map[int64]*user_model.User)
|
||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
if err := issues_model.NewIssue(ctx, repo, issue, labelIDs, uuids); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, assigneeID := range assigneeIDs {
|
||||
if _, err := AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assigneeID, true); err != nil {
|
||||
assignee, err := user_model.GetUserByID(ctx, assigneeID)
|
||||
if err != nil {
|
||||
log.Error("GetUserByID: %v", err)
|
||||
continue
|
||||
}
|
||||
assignees[assigneeID] = assignee
|
||||
comment, err := AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assignee)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
assigneeCommentMap[assigneeID] = comment
|
||||
}
|
||||
if len(projectIDs) > 0 {
|
||||
err := issues_model.IssueAssignOrRemoveProject(ctx, issue, issue.Poster, projectIDs)
|
||||
@@ -65,6 +75,12 @@ func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *issues_mo
|
||||
notify_service.IssueChangeMilestone(ctx, issue.Poster, issue, 0)
|
||||
}
|
||||
|
||||
if len(assigneeIDs) > 0 {
|
||||
for _, assignee := range assignees {
|
||||
notify_service.IssueChangeAssignee(ctx, issue.Poster, issue, assignee, false, assigneeCommentMap[assignee.ID])
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+10
-8
@@ -96,7 +96,7 @@ func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error {
|
||||
}
|
||||
|
||||
assigneeCommentMap := make(map[int64]*issues_model.Comment)
|
||||
|
||||
assignees := make(map[int64]*user_model.User)
|
||||
var reviewNotifiers []*issue_service.ReviewRequestNotifier
|
||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
if err := issues_model.NewPullRequest(ctx, repo, issue, labelIDs, uuids, pr); err != nil {
|
||||
@@ -104,10 +104,16 @@ func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error {
|
||||
}
|
||||
|
||||
for _, assigneeID := range assigneeIDs {
|
||||
comment, err := issue_service.AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assigneeID, false)
|
||||
assignee, err := user_model.GetUserByID(ctx, assigneeID)
|
||||
if err != nil {
|
||||
log.Error("GetUserByID: %v", err)
|
||||
continue
|
||||
}
|
||||
comment, err := issue_service.AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assignee)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
assignees[assigneeID] = assignee
|
||||
assigneeCommentMap[assigneeID] = comment
|
||||
}
|
||||
|
||||
@@ -186,12 +192,8 @@ func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error {
|
||||
if issue.Milestone != nil {
|
||||
notify_service.IssueChangeMilestone(ctx, issue.Poster, issue, 0)
|
||||
}
|
||||
for _, assigneeID := range assigneeIDs {
|
||||
assignee, err := user_model.GetUserByID(ctx, assigneeID)
|
||||
if err != nil {
|
||||
return ErrDependenciesLeft
|
||||
}
|
||||
notify_service.IssueChangeAssignee(ctx, issue.Poster, issue, assignee, false, assigneeCommentMap[assigneeID])
|
||||
for _, assignee := range assignees {
|
||||
notify_service.IssueChangeAssignee(ctx, issue.Poster, issue, assignee, false, assigneeCommentMap[assignee.ID])
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -100,7 +100,7 @@ func DeleteCollaboration(ctx context.Context, repo *repo_model.Repository, colla
|
||||
}
|
||||
|
||||
func ReconsiderRepoIssuesAssignee(ctx context.Context, repo *repo_model.Repository, user *user_model.User) error {
|
||||
if canAssigned, err := access_model.CanBeAssigned(ctx, user, repo, true); err != nil || canAssigned {
|
||||
if canAssigned, err := access_model.CanBeAssigned(ctx, user, repo); err != nil || canAssigned {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -11,16 +11,13 @@
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
{{end}}
|
||||
<div class="ui small secondary filter menu">
|
||||
<form id="org-member-search-form" class="ui form ignore-dirty tw-flex-1 tw-flex tw-items-center tw-gap-x-2">
|
||||
<div class="ui small secondary filter">
|
||||
<form id="org-member-search-form" class="ui form ignore-dirty tw-flex-1 tw-flex tw-items-center">
|
||||
<div class="ui small fluid action input tw-flex-1">
|
||||
{{template "shared/search/input" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.user_kind")}}
|
||||
{{template "shared/search/button"}}
|
||||
</div>
|
||||
</form>
|
||||
<div class="item tw-text-text-light">
|
||||
{{ctx.Locale.Tr "org.members"}}: {{CountFmt .MembersShown}} / {{CountFmt .MembersTotal}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-divided-list items-with-main">
|
||||
{{range .Members}}
|
||||
@@ -79,8 +76,8 @@
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="flex-item">
|
||||
<div class="flex-item-main">
|
||||
<div class="item">
|
||||
<div class="item-main">
|
||||
{{ctx.Locale.Tr "search.no_results"}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Generated
+237
@@ -6766,6 +6766,52 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/assignees/{assignee}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"repository"
|
||||
],
|
||||
"summary": "Check if a user can be assigned to issues in a repository",
|
||||
"operationId": "repoCheckAssignee",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "owner of the repo",
|
||||
"name": "owner",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the repo",
|
||||
"name": "repo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "username of the user to check for being an assignee",
|
||||
"name": "assignee",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"$ref": "#/responses/empty"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/avatar": {
|
||||
"post": {
|
||||
"produces": [
|
||||
@@ -10962,6 +11008,183 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/issues/{index}/assignees": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"issue"
|
||||
],
|
||||
"summary": "Add assignees to an issue",
|
||||
"operationId": "issueAddAssignees",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "owner of the repo",
|
||||
"name": "owner",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the repo",
|
||||
"name": "repo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "index of the issue",
|
||||
"name": "index",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/IssueAssigneesOption"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"$ref": "#/responses/Issue"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/responses/forbidden"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
},
|
||||
"422": {
|
||||
"$ref": "#/responses/validationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"issue"
|
||||
],
|
||||
"summary": "Remove assignees from an issue",
|
||||
"operationId": "issueRemoveAssignees",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "owner of the repo",
|
||||
"name": "owner",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the repo",
|
||||
"name": "repo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "index of the issue",
|
||||
"name": "index",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/IssueAssigneesOption"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/Issue"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/responses/forbidden"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
},
|
||||
"422": {
|
||||
"$ref": "#/responses/validationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/issues/{index}/assignees/{assignee}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"issue"
|
||||
],
|
||||
"summary": "Check if a user can be assigned to an issue",
|
||||
"operationId": "issueCheckAssignee",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "owner of the repo",
|
||||
"name": "owner",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the repo",
|
||||
"name": "repo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "index of the issue",
|
||||
"name": "index",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "username of the user to check for being an assignee",
|
||||
"name": "assignee",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"$ref": "#/responses/empty"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/issues/{index}/blocks": {
|
||||
"get": {
|
||||
"produces": [
|
||||
@@ -26838,6 +27061,20 @@
|
||||
},
|
||||
"x-go-package": "gitea.dev/modules/structs"
|
||||
},
|
||||
"IssueAssigneesOption": {
|
||||
"description": "IssueAssigneesOption options for adding/removing issue assignees",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"assignees": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"x-go-name": "Assignees"
|
||||
}
|
||||
},
|
||||
"x-go-package": "gitea.dev/modules/structs"
|
||||
},
|
||||
"IssueConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
Generated
+251
@@ -6992,6 +6992,20 @@
|
||||
"type": "object",
|
||||
"x-go-package": "gitea.dev/modules/structs"
|
||||
},
|
||||
"IssueAssigneesOption": {
|
||||
"description": "IssueAssigneesOption options for adding/removing issue assignees",
|
||||
"properties": {
|
||||
"assignees": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array",
|
||||
"x-go-name": "Assignees"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"x-go-package": "gitea.dev/modules/structs"
|
||||
},
|
||||
"IssueConfig": {
|
||||
"properties": {
|
||||
"blank_issues_enabled": {
|
||||
@@ -17814,6 +17828,55 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/assignees/{assignee}": {
|
||||
"get": {
|
||||
"operationId": "repoCheckAssignee",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "owner of the repo",
|
||||
"in": "path",
|
||||
"name": "owner",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "name of the repo",
|
||||
"in": "path",
|
||||
"name": "repo",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "username of the user to check for being an assignee",
|
||||
"in": "path",
|
||||
"name": "assignee",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"$ref": "#/components/responses/empty"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/components/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/components/responses/notFound"
|
||||
}
|
||||
},
|
||||
"summary": "Check if a user can be assigned to issues in a repository",
|
||||
"tags": [
|
||||
"repository"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/avatar": {
|
||||
"delete": {
|
||||
"operationId": "repoDeleteAvatar",
|
||||
@@ -22371,6 +22434,194 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/issues/{index}/assignees": {
|
||||
"delete": {
|
||||
"operationId": "issueRemoveAssignees",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "owner of the repo",
|
||||
"in": "path",
|
||||
"name": "owner",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "name of the repo",
|
||||
"in": "path",
|
||||
"name": "repo",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "index of the issue",
|
||||
"in": "path",
|
||||
"name": "index",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/IssueAssigneesOption"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true,
|
||||
"x-originalParamName": "body"
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/components/responses/Issue"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/components/responses/notFound"
|
||||
},
|
||||
"422": {
|
||||
"$ref": "#/components/responses/validationError"
|
||||
}
|
||||
},
|
||||
"summary": "Remove assignees from an issue",
|
||||
"tags": [
|
||||
"issue"
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"operationId": "issueAddAssignees",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "owner of the repo",
|
||||
"in": "path",
|
||||
"name": "owner",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "name of the repo",
|
||||
"in": "path",
|
||||
"name": "repo",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "index of the issue",
|
||||
"in": "path",
|
||||
"name": "index",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/IssueAssigneesOption"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true,
|
||||
"x-originalParamName": "body"
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"$ref": "#/components/responses/Issue"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/components/responses/error"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/components/responses/notFound"
|
||||
},
|
||||
"422": {
|
||||
"$ref": "#/components/responses/validationError"
|
||||
}
|
||||
},
|
||||
"summary": "Add assignees to an issue",
|
||||
"tags": [
|
||||
"issue"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/issues/{index}/assignees/{assignee}": {
|
||||
"get": {
|
||||
"operationId": "issueCheckAssignee",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "owner of the repo",
|
||||
"in": "path",
|
||||
"name": "owner",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "name of the repo",
|
||||
"in": "path",
|
||||
"name": "repo",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "index of the issue",
|
||||
"in": "path",
|
||||
"name": "index",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "username of the user to check for being an assignee",
|
||||
"in": "path",
|
||||
"name": "assignee",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"$ref": "#/components/responses/empty"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/components/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/components/responses/notFound"
|
||||
}
|
||||
},
|
||||
"summary": "Check if a user can be assigned to an issue",
|
||||
"tags": [
|
||||
"issue"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/issues/{index}/blocks": {
|
||||
"delete": {
|
||||
"operationId": "issueRemoveIssueBlocking",
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"time"
|
||||
|
||||
auth_model "gitea.dev/models/auth"
|
||||
"gitea.dev/models/db"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unittest"
|
||||
@@ -35,6 +36,7 @@ func TestAPIIssue(t *testing.T) {
|
||||
t.Run("IssueContentVersion", testAPIIssueContentVersion)
|
||||
t.Run("CreateIssue", testAPICreateIssue)
|
||||
t.Run("CreateIssueParallel", testAPICreateIssueParallel)
|
||||
t.Run("IssueAssignees", testAPIIssueAssignees)
|
||||
t.Run("IssueProjects", testAPIIssueProjects)
|
||||
}
|
||||
|
||||
@@ -495,6 +497,91 @@ func testAPIIssueContentVersion(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func testAPIIssueAssignees(t *testing.T) {
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
|
||||
session := loginUser(t, owner.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
|
||||
|
||||
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assignees", owner.Name, repo.Name, issue.Index)
|
||||
getAssigneeIDs := func(issueID int64) []int64 {
|
||||
assigneeIDs, err := issues_model.GetAssigneeIDsByIssue(t.Context(), issueID)
|
||||
assert.NoError(t, err)
|
||||
return assigneeIDs
|
||||
}
|
||||
|
||||
t.Run("NonWriter", func(t *testing.T) {
|
||||
req := NewRequestWithJSON(t, "POST", urlStr, &api.IssueAssigneesOption{Assignees: []string{"user40"}}).
|
||||
AddTokenAuth(getUserToken(t, "user5", auth_model.AccessTokenScopeWriteIssue))
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("MissingIssue", func(t *testing.T) {
|
||||
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/99999/assignees", owner.Name, repo.Name), &api.IssueAssigneesOption{Assignees: []string{"user40"}}).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("UnknownAssignee", func(t *testing.T) {
|
||||
req := NewRequestWithJSON(t, "POST", urlStr, &api.IssueAssigneesOption{Assignees: []string{"does-not-exist"}}).
|
||||
AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusUnprocessableEntity)
|
||||
apiErr := DecodeJSON(t, resp, &api.APIError{})
|
||||
assert.Equal(t, "user does not exist [uid: 0, name: does-not-exist]", apiErr.Message)
|
||||
})
|
||||
|
||||
t.Run("OrganizationAssignee", func(t *testing.T) {
|
||||
req := NewRequestWithJSON(t, "POST", urlStr, &api.IssueAssigneesOption{Assignees: []string{"org3"}}).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusBadRequest)
|
||||
|
||||
checkReq := NewRequest(t, "GET", fmt.Sprintf("%s/%s", urlStr, "org3")).AddTokenAuth(token)
|
||||
MakeRequest(t, checkReq, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("BlockedAssigneeIsAtomic", func(t *testing.T) {
|
||||
blockedIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 5})
|
||||
blockedURL := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assignees", owner.Name, repo.Name, blockedIssue.Index)
|
||||
blockedToken := getUserToken(t, "user40", auth_model.AccessTokenScopeWriteIssue)
|
||||
|
||||
assert.Empty(t, getAssigneeIDs(blockedIssue.ID))
|
||||
assert.NoError(t, db.Insert(t.Context(), &user_model.Blocking{
|
||||
BlockerID: owner.ID,
|
||||
BlockeeID: 40,
|
||||
}))
|
||||
|
||||
req := NewRequestWithJSON(t, "POST", blockedURL, &api.IssueAssigneesOption{Assignees: []string{"user1", "user2"}}).
|
||||
AddTokenAuth(blockedToken)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
assert.Empty(t, getAssigneeIDs(blockedIssue.ID))
|
||||
})
|
||||
|
||||
t.Run("HappyPath", func(t *testing.T) {
|
||||
req := NewRequestWithJSON(t, "POST", urlStr, &api.IssueAssigneesOption{Assignees: []string{"user40"}}).
|
||||
AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusCreated)
|
||||
apiIssue := DecodeJSON(t, resp, &api.Issue{})
|
||||
assert.Len(t, apiIssue.Assignees, 2)
|
||||
assert.ElementsMatch(t, []int64{1, 40}, []int64{apiIssue.Assignees[0].ID, apiIssue.Assignees[1].ID})
|
||||
|
||||
checkReq := NewRequest(t, "GET", fmt.Sprintf("%s/%s", urlStr, "user40")).AddTokenAuth(token)
|
||||
MakeRequest(t, checkReq, http.StatusNoContent)
|
||||
|
||||
// This endpoint checks assignability, not current assignment membership.
|
||||
checkReq = NewRequest(t, "GET", fmt.Sprintf("%s/%s", urlStr, "user5")).AddTokenAuth(token)
|
||||
MakeRequest(t, checkReq, http.StatusNoContent)
|
||||
|
||||
req = NewRequestWithJSON(t, "DELETE", urlStr, &api.IssueAssigneesOption{Assignees: []string{"user1"}}).
|
||||
AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
apiIssue = DecodeJSON(t, resp, &api.Issue{})
|
||||
assert.Len(t, apiIssue.Assignees, 1)
|
||||
assert.Equal(t, int64(40), apiIssue.Assignees[0].ID)
|
||||
})
|
||||
}
|
||||
|
||||
func testAPIIssueProjects(t *testing.T) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
|
||||
@@ -721,11 +721,25 @@ func TestAPIRepoGetAssignees(t *testing.T) {
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
session := loginUser(t, user.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||
|
||||
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees", user.Name, repo.Name).
|
||||
AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
assignees := DecodeJSON(t, resp, []*api.User{})
|
||||
assert.Len(t, assignees, 2)
|
||||
assert.Len(t, assignees, 1)
|
||||
|
||||
assignee := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees/%s", user.Name, repo.Name, assignee.Name).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
nonAssignee := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
|
||||
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees/%s", user.Name, repo.Name, nonAssignee.Name).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees/%s", user.Name, repo.Name, "org3").
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user