gitea源码

cargo.go 8.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package cargo
  4. import (
  5. "errors"
  6. "fmt"
  7. "net/http"
  8. "strconv"
  9. "strings"
  10. "code.gitea.io/gitea/models/db"
  11. packages_model "code.gitea.io/gitea/models/packages"
  12. "code.gitea.io/gitea/modules/log"
  13. "code.gitea.io/gitea/modules/optional"
  14. packages_module "code.gitea.io/gitea/modules/packages"
  15. cargo_module "code.gitea.io/gitea/modules/packages/cargo"
  16. "code.gitea.io/gitea/modules/setting"
  17. "code.gitea.io/gitea/modules/structs"
  18. "code.gitea.io/gitea/modules/util"
  19. "code.gitea.io/gitea/routers/api/packages/helper"
  20. "code.gitea.io/gitea/services/context"
  21. "code.gitea.io/gitea/services/convert"
  22. packages_service "code.gitea.io/gitea/services/packages"
  23. cargo_service "code.gitea.io/gitea/services/packages/cargo"
  24. )
  25. // https://doc.rust-lang.org/cargo/reference/registries.html#web-api
  26. type StatusResponse struct {
  27. OK bool `json:"ok"`
  28. Errors []StatusMessage `json:"errors,omitempty"`
  29. }
  30. type StatusMessage struct {
  31. Message string `json:"detail"`
  32. }
  33. func apiError(ctx *context.Context, status int, obj any) {
  34. message := helper.ProcessErrorForUser(ctx, status, obj)
  35. ctx.JSON(status, StatusResponse{
  36. OK: false,
  37. Errors: []StatusMessage{
  38. {
  39. Message: message,
  40. },
  41. },
  42. })
  43. }
  44. // https://rust-lang.github.io/rfcs/2789-sparse-index.html
  45. func RepositoryConfig(ctx *context.Context) {
  46. ctx.JSON(http.StatusOK, cargo_service.BuildConfig(ctx.Package.Owner, setting.Service.RequireSignInViewStrict || ctx.Package.Owner.Visibility != structs.VisibleTypePublic))
  47. }
  48. func EnumeratePackageVersions(ctx *context.Context) {
  49. p, err := packages_model.GetPackageByName(ctx, ctx.Package.Owner.ID, packages_model.TypeCargo, ctx.PathParam("package"))
  50. if err != nil {
  51. if errors.Is(err, util.ErrNotExist) {
  52. apiError(ctx, http.StatusNotFound, err)
  53. } else {
  54. apiError(ctx, http.StatusInternalServerError, err)
  55. }
  56. return
  57. }
  58. b, err := cargo_service.BuildPackageIndex(ctx, p)
  59. if err != nil {
  60. apiError(ctx, http.StatusInternalServerError, err)
  61. return
  62. }
  63. if b == nil {
  64. apiError(ctx, http.StatusNotFound, nil)
  65. return
  66. }
  67. ctx.PlainTextBytes(http.StatusOK, b.Bytes())
  68. }
  69. type SearchResult struct {
  70. Crates []*SearchResultCrate `json:"crates"`
  71. Meta SearchResultMeta `json:"meta"`
  72. }
  73. type SearchResultCrate struct {
  74. Name string `json:"name"`
  75. LatestVersion string `json:"max_version"`
  76. Description string `json:"description"`
  77. }
  78. type SearchResultMeta struct {
  79. Total int64 `json:"total"`
  80. }
  81. // https://doc.rust-lang.org/cargo/reference/registries.html#search
  82. func SearchPackages(ctx *context.Context) {
  83. page := max(ctx.FormInt("page"), 1)
  84. perPage := ctx.FormInt("per_page")
  85. paginator := db.ListOptions{
  86. Page: page,
  87. PageSize: convert.ToCorrectPageSize(perPage),
  88. }
  89. pvs, total, err := packages_model.SearchLatestVersions(
  90. ctx,
  91. &packages_model.PackageSearchOptions{
  92. OwnerID: ctx.Package.Owner.ID,
  93. Type: packages_model.TypeCargo,
  94. Name: packages_model.SearchValue{Value: ctx.FormTrim("q")},
  95. IsInternal: optional.Some(false),
  96. Paginator: &paginator,
  97. },
  98. )
  99. if err != nil {
  100. apiError(ctx, http.StatusInternalServerError, err)
  101. return
  102. }
  103. pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
  104. if err != nil {
  105. apiError(ctx, http.StatusInternalServerError, err)
  106. return
  107. }
  108. crates := make([]*SearchResultCrate, 0, len(pvs))
  109. for _, pd := range pds {
  110. crates = append(crates, &SearchResultCrate{
  111. Name: pd.Package.Name,
  112. LatestVersion: pd.Version.Version,
  113. Description: pd.Metadata.(*cargo_module.Metadata).Description,
  114. })
  115. }
  116. ctx.JSON(http.StatusOK, SearchResult{
  117. Crates: crates,
  118. Meta: SearchResultMeta{
  119. Total: total,
  120. },
  121. })
  122. }
  123. type Owners struct {
  124. Users []OwnerUser `json:"users"`
  125. }
  126. type OwnerUser struct {
  127. ID int64 `json:"id"`
  128. Login string `json:"login"`
  129. Name string `json:"name"`
  130. }
  131. // https://doc.rust-lang.org/cargo/reference/registries.html#owners-list
  132. func ListOwners(ctx *context.Context) {
  133. ctx.JSON(http.StatusOK, Owners{
  134. Users: []OwnerUser{
  135. {
  136. ID: ctx.Package.Owner.ID,
  137. Login: ctx.Package.Owner.Name,
  138. Name: ctx.Package.Owner.DisplayName(),
  139. },
  140. },
  141. })
  142. }
  143. // DownloadPackageFile serves the content of a package
  144. func DownloadPackageFile(ctx *context.Context) {
  145. s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
  146. ctx,
  147. &packages_service.PackageInfo{
  148. Owner: ctx.Package.Owner,
  149. PackageType: packages_model.TypeCargo,
  150. Name: ctx.PathParam("package"),
  151. Version: ctx.PathParam("version"),
  152. },
  153. &packages_service.PackageFileInfo{
  154. Filename: strings.ToLower(fmt.Sprintf("%s-%s.crate", ctx.PathParam("package"), ctx.PathParam("version"))),
  155. },
  156. ctx.Req.Method,
  157. )
  158. if err != nil {
  159. if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, packages_model.ErrPackageFileNotExist) {
  160. apiError(ctx, http.StatusNotFound, err)
  161. return
  162. }
  163. apiError(ctx, http.StatusInternalServerError, err)
  164. return
  165. }
  166. helper.ServePackageFile(ctx, s, u, pf)
  167. }
  168. // https://doc.rust-lang.org/cargo/reference/registries.html#publish
  169. func UploadPackage(ctx *context.Context) {
  170. defer ctx.Req.Body.Close()
  171. cp, err := cargo_module.ParsePackage(ctx.Req.Body)
  172. if err != nil {
  173. apiError(ctx, http.StatusBadRequest, err)
  174. return
  175. }
  176. buf, err := packages_module.CreateHashedBufferFromReader(cp.Content)
  177. if err != nil {
  178. apiError(ctx, http.StatusInternalServerError, err)
  179. return
  180. }
  181. defer buf.Close()
  182. if buf.Size() != cp.ContentSize {
  183. apiError(ctx, http.StatusBadRequest, "invalid content size")
  184. return
  185. }
  186. pv, _, err := packages_service.CreatePackageAndAddFile(
  187. ctx,
  188. &packages_service.PackageCreationInfo{
  189. PackageInfo: packages_service.PackageInfo{
  190. Owner: ctx.Package.Owner,
  191. PackageType: packages_model.TypeCargo,
  192. Name: cp.Name,
  193. Version: cp.Version,
  194. },
  195. SemverCompatible: true,
  196. Creator: ctx.Doer,
  197. Metadata: cp.Metadata,
  198. VersionProperties: map[string]string{
  199. cargo_module.PropertyYanked: strconv.FormatBool(false),
  200. },
  201. },
  202. &packages_service.PackageFileCreationInfo{
  203. PackageFileInfo: packages_service.PackageFileInfo{
  204. Filename: strings.ToLower(fmt.Sprintf("%s-%s.crate", cp.Name, cp.Version)),
  205. },
  206. Creator: ctx.Doer,
  207. Data: buf,
  208. IsLead: true,
  209. },
  210. )
  211. if err != nil {
  212. switch err {
  213. case packages_model.ErrDuplicatePackageVersion:
  214. apiError(ctx, http.StatusConflict, err)
  215. case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
  216. apiError(ctx, http.StatusForbidden, err)
  217. default:
  218. apiError(ctx, http.StatusInternalServerError, err)
  219. }
  220. return
  221. }
  222. if err := cargo_service.UpdatePackageIndexIfExists(ctx, ctx.Doer, ctx.Package.Owner, pv.PackageID); err != nil {
  223. if err := packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil {
  224. log.Error("Rollback creation of package version: %v", err)
  225. }
  226. apiError(ctx, http.StatusInternalServerError, err)
  227. return
  228. }
  229. ctx.JSON(http.StatusOK, StatusResponse{OK: true})
  230. }
  231. // https://doc.rust-lang.org/cargo/reference/registries.html#yank
  232. func YankPackage(ctx *context.Context) {
  233. yankPackage(ctx, true)
  234. }
  235. // https://doc.rust-lang.org/cargo/reference/registries.html#unyank
  236. func UnyankPackage(ctx *context.Context) {
  237. yankPackage(ctx, false)
  238. }
  239. func yankPackage(ctx *context.Context, yank bool) {
  240. pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeCargo, ctx.PathParam("package"), ctx.PathParam("version"))
  241. if err != nil {
  242. if errors.Is(err, packages_model.ErrPackageNotExist) {
  243. apiError(ctx, http.StatusNotFound, err)
  244. return
  245. }
  246. apiError(ctx, http.StatusInternalServerError, err)
  247. return
  248. }
  249. pps, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, cargo_module.PropertyYanked)
  250. if err != nil {
  251. apiError(ctx, http.StatusInternalServerError, err)
  252. return
  253. }
  254. if len(pps) == 0 {
  255. apiError(ctx, http.StatusInternalServerError, "Property not found")
  256. return
  257. }
  258. pp := pps[0]
  259. pp.Value = strconv.FormatBool(yank)
  260. if err := packages_model.UpdateProperty(ctx, pp); err != nil {
  261. apiError(ctx, http.StatusInternalServerError, err)
  262. return
  263. }
  264. if err := cargo_service.UpdatePackageIndexIfExists(ctx, ctx.Doer, ctx.Package.Owner, pv.PackageID); err != nil {
  265. apiError(ctx, http.StatusInternalServerError, err)
  266. return
  267. }
  268. ctx.JSON(http.StatusOK, StatusResponse{OK: true})
  269. }