gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. // Copyright 2021 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package composer
  4. import (
  5. "errors"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "net/url"
  10. "strconv"
  11. "strings"
  12. "code.gitea.io/gitea/models/db"
  13. packages_model "code.gitea.io/gitea/models/packages"
  14. "code.gitea.io/gitea/modules/optional"
  15. packages_module "code.gitea.io/gitea/modules/packages"
  16. composer_module "code.gitea.io/gitea/modules/packages/composer"
  17. "code.gitea.io/gitea/modules/setting"
  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. "github.com/hashicorp/go-version"
  24. )
  25. func apiError(ctx *context.Context, status int, obj any) {
  26. message := helper.ProcessErrorForUser(ctx, status, obj)
  27. type Error struct {
  28. Status int `json:"status"`
  29. Message string `json:"message"`
  30. }
  31. ctx.JSON(status, struct {
  32. Errors []Error `json:"errors"`
  33. }{
  34. Errors: []Error{
  35. {Status: status, Message: message},
  36. },
  37. })
  38. }
  39. // ServiceIndex displays registry endpoints
  40. func ServiceIndex(ctx *context.Context) {
  41. resp := createServiceIndexResponse(setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/composer")
  42. ctx.JSON(http.StatusOK, resp)
  43. }
  44. // SearchPackages searches packages, only "q" is supported
  45. // https://packagist.org/apidoc#search-packages
  46. func SearchPackages(ctx *context.Context) {
  47. page := max(ctx.FormInt("page"), 1)
  48. perPage := ctx.FormInt("per_page")
  49. paginator := db.ListOptions{
  50. Page: page,
  51. PageSize: convert.ToCorrectPageSize(perPage),
  52. }
  53. opts := &packages_model.PackageSearchOptions{
  54. OwnerID: ctx.Package.Owner.ID,
  55. Type: packages_model.TypeComposer,
  56. Name: packages_model.SearchValue{Value: ctx.FormTrim("q")},
  57. IsInternal: optional.Some(false),
  58. Paginator: &paginator,
  59. }
  60. if ctx.FormTrim("type") != "" {
  61. opts.Properties = map[string]string{
  62. composer_module.TypeProperty: ctx.FormTrim("type"),
  63. }
  64. }
  65. pvs, total, err := packages_model.SearchLatestVersions(ctx, opts)
  66. if err != nil {
  67. apiError(ctx, http.StatusInternalServerError, err)
  68. return
  69. }
  70. nextLink := ""
  71. if len(pvs) == paginator.PageSize {
  72. u, err := url.Parse(setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/composer/search.json")
  73. if err != nil {
  74. apiError(ctx, http.StatusInternalServerError, err)
  75. return
  76. }
  77. q := u.Query()
  78. q.Set("q", ctx.FormTrim("q"))
  79. q.Set("type", ctx.FormTrim("type"))
  80. q.Set("page", strconv.Itoa(page+1))
  81. if perPage != 0 {
  82. q.Set("per_page", strconv.Itoa(perPage))
  83. }
  84. u.RawQuery = q.Encode()
  85. nextLink = u.String()
  86. }
  87. pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
  88. if err != nil {
  89. apiError(ctx, http.StatusInternalServerError, err)
  90. return
  91. }
  92. resp := createSearchResultResponse(total, pds, nextLink)
  93. ctx.JSON(http.StatusOK, resp)
  94. }
  95. // EnumeratePackages lists all package names
  96. // https://packagist.org/apidoc#list-packages
  97. func EnumeratePackages(ctx *context.Context) {
  98. ps, err := packages_model.GetPackagesByType(ctx, ctx.Package.Owner.ID, packages_model.TypeComposer)
  99. if err != nil {
  100. apiError(ctx, http.StatusInternalServerError, err)
  101. return
  102. }
  103. names := make([]string, 0, len(ps))
  104. for _, p := range ps {
  105. names = append(names, p.Name)
  106. }
  107. ctx.JSON(http.StatusOK, map[string][]string{
  108. "packageNames": names,
  109. })
  110. }
  111. // PackageMetadata returns the metadata for a single package
  112. // https://packagist.org/apidoc#get-package-data
  113. func PackageMetadata(ctx *context.Context) {
  114. vendorName := ctx.PathParam("vendorname")
  115. projectName := ctx.PathParam("projectname")
  116. pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeComposer, vendorName+"/"+projectName)
  117. if err != nil {
  118. apiError(ctx, http.StatusInternalServerError, err)
  119. return
  120. }
  121. if len(pvs) == 0 {
  122. apiError(ctx, http.StatusNotFound, packages_model.ErrPackageNotExist)
  123. return
  124. }
  125. pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
  126. if err != nil {
  127. apiError(ctx, http.StatusInternalServerError, err)
  128. return
  129. }
  130. resp := createPackageMetadataResponse(
  131. setting.AppURL+"api/packages/"+ctx.Package.Owner.Name+"/composer",
  132. pds,
  133. )
  134. ctx.JSON(http.StatusOK, resp)
  135. }
  136. // DownloadPackageFile serves the content of a package
  137. func DownloadPackageFile(ctx *context.Context) {
  138. s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
  139. ctx,
  140. &packages_service.PackageInfo{
  141. Owner: ctx.Package.Owner,
  142. PackageType: packages_model.TypeComposer,
  143. Name: ctx.PathParam("package"),
  144. Version: ctx.PathParam("version"),
  145. },
  146. &packages_service.PackageFileInfo{
  147. Filename: ctx.PathParam("filename"),
  148. },
  149. ctx.Req.Method,
  150. )
  151. if err != nil {
  152. if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, packages_model.ErrPackageFileNotExist) {
  153. apiError(ctx, http.StatusNotFound, err)
  154. return
  155. }
  156. apiError(ctx, http.StatusInternalServerError, err)
  157. return
  158. }
  159. helper.ServePackageFile(ctx, s, u, pf)
  160. }
  161. // UploadPackage creates a new package
  162. func UploadPackage(ctx *context.Context) {
  163. buf, err := packages_module.CreateHashedBufferFromReader(ctx.Req.Body)
  164. if err != nil {
  165. apiError(ctx, http.StatusInternalServerError, err)
  166. return
  167. }
  168. defer buf.Close()
  169. cp, err := composer_module.ParsePackage(buf, buf.Size())
  170. if err != nil {
  171. if errors.Is(err, util.ErrInvalidArgument) {
  172. apiError(ctx, http.StatusBadRequest, err)
  173. } else {
  174. apiError(ctx, http.StatusInternalServerError, err)
  175. }
  176. return
  177. }
  178. if _, err := buf.Seek(0, io.SeekStart); err != nil {
  179. apiError(ctx, http.StatusInternalServerError, err)
  180. return
  181. }
  182. if cp.Version == "" {
  183. v, err := version.NewVersion(ctx.FormTrim("version"))
  184. if err != nil {
  185. apiError(ctx, http.StatusBadRequest, composer_module.ErrInvalidVersion)
  186. return
  187. }
  188. cp.Version = v.String()
  189. }
  190. _, _, err = packages_service.CreatePackageAndAddFile(
  191. ctx,
  192. &packages_service.PackageCreationInfo{
  193. PackageInfo: packages_service.PackageInfo{
  194. Owner: ctx.Package.Owner,
  195. PackageType: packages_model.TypeComposer,
  196. Name: cp.Name,
  197. Version: cp.Version,
  198. },
  199. SemverCompatible: true,
  200. Creator: ctx.Doer,
  201. Metadata: cp.Metadata,
  202. VersionProperties: map[string]string{
  203. composer_module.TypeProperty: cp.Type,
  204. },
  205. },
  206. &packages_service.PackageFileCreationInfo{
  207. PackageFileInfo: packages_service.PackageFileInfo{
  208. Filename: strings.ToLower(fmt.Sprintf("%s.%s.zip", strings.ReplaceAll(cp.Name, "/", "-"), cp.Version)),
  209. },
  210. Creator: ctx.Doer,
  211. Data: buf,
  212. IsLead: true,
  213. },
  214. )
  215. if err != nil {
  216. switch err {
  217. case packages_model.ErrDuplicatePackageVersion:
  218. apiError(ctx, http.StatusConflict, err)
  219. case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
  220. apiError(ctx, http.StatusForbidden, err)
  221. default:
  222. apiError(ctx, http.StatusInternalServerError, err)
  223. }
  224. return
  225. }
  226. ctx.Status(http.StatusCreated)
  227. }