gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package conan
  4. import (
  5. std_ctx "context"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "net/http"
  10. "strings"
  11. "time"
  12. auth_model "code.gitea.io/gitea/models/auth"
  13. "code.gitea.io/gitea/models/db"
  14. packages_model "code.gitea.io/gitea/models/packages"
  15. conan_model "code.gitea.io/gitea/models/packages/conan"
  16. "code.gitea.io/gitea/modules/container"
  17. "code.gitea.io/gitea/modules/json"
  18. "code.gitea.io/gitea/modules/log"
  19. packages_module "code.gitea.io/gitea/modules/packages"
  20. conan_module "code.gitea.io/gitea/modules/packages/conan"
  21. "code.gitea.io/gitea/modules/setting"
  22. "code.gitea.io/gitea/routers/api/packages/helper"
  23. auth_service "code.gitea.io/gitea/services/auth"
  24. "code.gitea.io/gitea/services/context"
  25. notify_service "code.gitea.io/gitea/services/notify"
  26. packages_service "code.gitea.io/gitea/services/packages"
  27. )
  28. const (
  29. conanfileFile = "conanfile.py"
  30. conaninfoFile = "conaninfo.txt"
  31. recipeReferenceKey = "RecipeReference"
  32. packageReferenceKey = "PackageReference"
  33. )
  34. var (
  35. recipeFileList = container.SetOf(
  36. conanfileFile,
  37. "conanmanifest.txt",
  38. "conan_sources.tgz",
  39. "conan_export.tgz",
  40. )
  41. packageFileList = container.SetOf(
  42. conaninfoFile,
  43. "conanmanifest.txt",
  44. "conan_package.tgz",
  45. )
  46. )
  47. func jsonResponse(ctx *context.Context, status int, obj any) {
  48. // https://github.com/conan-io/conan/issues/6613
  49. ctx.Resp.Header().Set("Content-Type", "application/json")
  50. ctx.Status(status)
  51. _ = json.NewEncoder(ctx.Resp).Encode(obj)
  52. }
  53. func apiError(ctx *context.Context, status int, obj any) {
  54. message := helper.ProcessErrorForUser(ctx, status, obj)
  55. jsonResponse(ctx, status, map[string]string{
  56. "message": message,
  57. })
  58. }
  59. func baseURL(ctx *context.Context) string {
  60. return setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/conan"
  61. }
  62. // ExtractPathParameters is a middleware to extract common parameters from path
  63. func ExtractPathParameters(ctx *context.Context) {
  64. rref, err := conan_module.NewRecipeReference(
  65. ctx.PathParam("name"),
  66. ctx.PathParam("version"),
  67. ctx.PathParam("user"),
  68. ctx.PathParam("channel"),
  69. ctx.PathParam("recipe_revision"),
  70. )
  71. if err != nil {
  72. apiError(ctx, http.StatusBadRequest, err)
  73. return
  74. }
  75. ctx.Data[recipeReferenceKey] = rref
  76. reference := ctx.PathParam("package_reference")
  77. var pref *conan_module.PackageReference
  78. if reference != "" {
  79. pref, err = conan_module.NewPackageReference(
  80. rref,
  81. reference,
  82. ctx.PathParam("package_revision"),
  83. )
  84. if err != nil {
  85. apiError(ctx, http.StatusBadRequest, err)
  86. return
  87. }
  88. }
  89. ctx.Data[packageReferenceKey] = pref
  90. }
  91. // Ping reports the server capabilities
  92. func Ping(ctx *context.Context) {
  93. ctx.RespHeader().Add("X-Conan-Server-Capabilities", "revisions") // complex_search,checksum_deploy,matrix_params
  94. ctx.Status(http.StatusOK)
  95. }
  96. // Authenticate creates an authentication token for the user
  97. func Authenticate(ctx *context.Context) {
  98. if ctx.Doer == nil {
  99. apiError(ctx, http.StatusBadRequest, nil)
  100. return
  101. }
  102. packageScope := auth_service.GetAccessScope(ctx.Data)
  103. if has, err := packageScope.HasAnyScope(
  104. auth_model.AccessTokenScopeReadPackage,
  105. auth_model.AccessTokenScopeWritePackage,
  106. auth_model.AccessTokenScopeAll,
  107. ); !has {
  108. if err != nil {
  109. log.Error("Error checking access scope: %v", err)
  110. }
  111. apiError(ctx, http.StatusForbidden, nil)
  112. return
  113. }
  114. token, err := packages_service.CreateAuthorizationToken(ctx.Doer, packageScope)
  115. if err != nil {
  116. apiError(ctx, http.StatusInternalServerError, err)
  117. return
  118. }
  119. ctx.PlainText(http.StatusOK, token)
  120. }
  121. // CheckCredentials tests if the provided authentication token is valid
  122. func CheckCredentials(ctx *context.Context) {
  123. if ctx.Doer == nil {
  124. ctx.Status(http.StatusUnauthorized)
  125. return
  126. }
  127. packageScope := auth_service.GetAccessScope(ctx.Data)
  128. if has, err := packageScope.HasAnyScope(
  129. auth_model.AccessTokenScopeReadPackage,
  130. auth_model.AccessTokenScopeWritePackage,
  131. auth_model.AccessTokenScopeAll,
  132. ); !has {
  133. if err != nil {
  134. log.Error("Error checking access scope: %v", err)
  135. }
  136. ctx.Status(http.StatusForbidden)
  137. return
  138. }
  139. ctx.Status(http.StatusOK)
  140. }
  141. // RecipeSnapshot displays the recipe files with their md5 hash
  142. func RecipeSnapshot(ctx *context.Context) {
  143. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  144. serveSnapshot(ctx, rref.AsKey())
  145. }
  146. // RecipeSnapshot displays the package files with their md5 hash
  147. func PackageSnapshot(ctx *context.Context) {
  148. pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
  149. serveSnapshot(ctx, pref.AsKey())
  150. }
  151. func serveSnapshot(ctx *context.Context, fileKey string) {
  152. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  153. pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeConan, rref.Name, rref.Version)
  154. if err != nil {
  155. if errors.Is(err, packages_model.ErrPackageNotExist) {
  156. apiError(ctx, http.StatusNotFound, err)
  157. } else {
  158. apiError(ctx, http.StatusInternalServerError, err)
  159. }
  160. return
  161. }
  162. pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
  163. VersionID: pv.ID,
  164. CompositeKey: fileKey,
  165. })
  166. if err != nil {
  167. apiError(ctx, http.StatusInternalServerError, err)
  168. return
  169. }
  170. if len(pfs) == 0 {
  171. apiError(ctx, http.StatusNotFound, nil)
  172. return
  173. }
  174. files := make(map[string]string)
  175. for _, pf := range pfs {
  176. pb, err := packages_model.GetBlobByID(ctx, pf.BlobID)
  177. if err != nil {
  178. apiError(ctx, http.StatusInternalServerError, err)
  179. return
  180. }
  181. files[pf.Name] = pb.HashMD5
  182. }
  183. jsonResponse(ctx, http.StatusOK, files)
  184. }
  185. // RecipeDownloadURLs displays the recipe files with their download url
  186. func RecipeDownloadURLs(ctx *context.Context) {
  187. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  188. serveDownloadURLs(
  189. ctx,
  190. rref.AsKey(),
  191. fmt.Sprintf(baseURL(ctx)+"/v1/files/%s/recipe", rref.LinkName()),
  192. )
  193. }
  194. // PackageDownloadURLs displays the package files with their download url
  195. func PackageDownloadURLs(ctx *context.Context) {
  196. pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
  197. serveDownloadURLs(
  198. ctx,
  199. pref.AsKey(),
  200. fmt.Sprintf(baseURL(ctx)+"/v1/files/%s/package/%s", pref.Recipe.LinkName(), pref.LinkName()),
  201. )
  202. }
  203. func serveDownloadURLs(ctx *context.Context, fileKey, downloadURL string) {
  204. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  205. pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeConan, rref.Name, rref.Version)
  206. if err != nil {
  207. if errors.Is(err, packages_model.ErrPackageNotExist) {
  208. apiError(ctx, http.StatusNotFound, err)
  209. } else {
  210. apiError(ctx, http.StatusInternalServerError, err)
  211. }
  212. return
  213. }
  214. pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
  215. VersionID: pv.ID,
  216. CompositeKey: fileKey,
  217. })
  218. if err != nil {
  219. apiError(ctx, http.StatusInternalServerError, err)
  220. return
  221. }
  222. if len(pfs) == 0 {
  223. apiError(ctx, http.StatusNotFound, nil)
  224. return
  225. }
  226. urls := make(map[string]string)
  227. for _, pf := range pfs {
  228. urls[pf.Name] = fmt.Sprintf("%s/%s", downloadURL, pf.Name)
  229. }
  230. jsonResponse(ctx, http.StatusOK, urls)
  231. }
  232. // RecipeUploadURLs displays the upload urls for the provided recipe files
  233. func RecipeUploadURLs(ctx *context.Context) {
  234. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  235. serveUploadURLs(
  236. ctx,
  237. recipeFileList,
  238. fmt.Sprintf(baseURL(ctx)+"/v1/files/%s/recipe", rref.LinkName()),
  239. )
  240. }
  241. // PackageUploadURLs displays the upload urls for the provided package files
  242. func PackageUploadURLs(ctx *context.Context) {
  243. pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
  244. serveUploadURLs(
  245. ctx,
  246. packageFileList,
  247. fmt.Sprintf(baseURL(ctx)+"/v1/files/%s/package/%s", pref.Recipe.LinkName(), pref.LinkName()),
  248. )
  249. }
  250. func serveUploadURLs(ctx *context.Context, fileFilter container.Set[string], uploadURL string) {
  251. defer ctx.Req.Body.Close()
  252. var files map[string]int64
  253. if err := json.NewDecoder(ctx.Req.Body).Decode(&files); err != nil {
  254. apiError(ctx, http.StatusBadRequest, err)
  255. return
  256. }
  257. urls := make(map[string]string)
  258. for file := range files {
  259. if fileFilter.Contains(file) {
  260. urls[file] = fmt.Sprintf("%s/%s", uploadURL, file)
  261. }
  262. }
  263. jsonResponse(ctx, http.StatusOK, urls)
  264. }
  265. // UploadRecipeFile handles the upload of a recipe file
  266. func UploadRecipeFile(ctx *context.Context) {
  267. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  268. uploadFile(ctx, recipeFileList, rref.AsKey())
  269. }
  270. // UploadPackageFile handles the upload of a package file
  271. func UploadPackageFile(ctx *context.Context) {
  272. pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
  273. uploadFile(ctx, packageFileList, pref.AsKey())
  274. }
  275. func uploadFile(ctx *context.Context, fileFilter container.Set[string], fileKey string) {
  276. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  277. pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
  278. filename := ctx.PathParam("filename")
  279. if !fileFilter.Contains(filename) {
  280. apiError(ctx, http.StatusBadRequest, nil)
  281. return
  282. }
  283. upload, needToClose, err := ctx.UploadStream()
  284. if err != nil {
  285. apiError(ctx, http.StatusBadRequest, err)
  286. return
  287. }
  288. if needToClose {
  289. defer upload.Close()
  290. }
  291. buf, err := packages_module.CreateHashedBufferFromReader(upload)
  292. if err != nil {
  293. apiError(ctx, http.StatusInternalServerError, err)
  294. return
  295. }
  296. defer buf.Close()
  297. isConanfileFile := filename == conanfileFile
  298. isConaninfoFile := filename == conaninfoFile
  299. pci := &packages_service.PackageCreationInfo{
  300. PackageInfo: packages_service.PackageInfo{
  301. Owner: ctx.Package.Owner,
  302. PackageType: packages_model.TypeConan,
  303. Name: rref.Name,
  304. Version: rref.Version,
  305. },
  306. Creator: ctx.Doer,
  307. }
  308. pfci := &packages_service.PackageFileCreationInfo{
  309. PackageFileInfo: packages_service.PackageFileInfo{
  310. Filename: strings.ToLower(filename),
  311. CompositeKey: fileKey,
  312. },
  313. Creator: ctx.Doer,
  314. Data: buf,
  315. IsLead: isConanfileFile,
  316. Properties: map[string]string{
  317. conan_module.PropertyRecipeUser: rref.User,
  318. conan_module.PropertyRecipeChannel: rref.Channel,
  319. conan_module.PropertyRecipeRevision: rref.RevisionOrDefault(),
  320. },
  321. OverwriteExisting: true,
  322. }
  323. if pref != nil {
  324. pfci.Properties[conan_module.PropertyPackageReference] = pref.Reference
  325. pfci.Properties[conan_module.PropertyPackageRevision] = pref.RevisionOrDefault()
  326. }
  327. if isConanfileFile || isConaninfoFile {
  328. if isConanfileFile {
  329. metadata, err := conan_module.ParseConanfile(buf)
  330. if err != nil {
  331. apiError(ctx, http.StatusInternalServerError, err)
  332. return
  333. }
  334. pv, err := packages_model.GetVersionByNameAndVersion(ctx, pci.Owner.ID, pci.PackageType, pci.Name, pci.Version)
  335. if err != nil && err != packages_model.ErrPackageNotExist {
  336. apiError(ctx, http.StatusInternalServerError, err)
  337. return
  338. }
  339. if pv != nil {
  340. raw, err := json.Marshal(metadata)
  341. if err != nil {
  342. apiError(ctx, http.StatusInternalServerError, err)
  343. return
  344. }
  345. pv.MetadataJSON = string(raw)
  346. if err := packages_model.UpdateVersion(ctx, pv); err != nil {
  347. apiError(ctx, http.StatusInternalServerError, err)
  348. return
  349. }
  350. } else {
  351. pci.Metadata = metadata
  352. }
  353. } else {
  354. info, err := conan_module.ParseConaninfo(buf)
  355. if err != nil {
  356. apiError(ctx, http.StatusInternalServerError, err)
  357. return
  358. }
  359. raw, err := json.Marshal(info)
  360. if err != nil {
  361. apiError(ctx, http.StatusInternalServerError, err)
  362. return
  363. }
  364. pfci.Properties[conan_module.PropertyPackageInfo] = string(raw)
  365. }
  366. if _, err := buf.Seek(0, io.SeekStart); err != nil {
  367. apiError(ctx, http.StatusInternalServerError, err)
  368. return
  369. }
  370. }
  371. _, _, err = packages_service.CreatePackageOrAddFileToExisting(
  372. ctx,
  373. pci,
  374. pfci,
  375. )
  376. if err != nil {
  377. switch err {
  378. case packages_model.ErrDuplicatePackageFile:
  379. apiError(ctx, http.StatusConflict, err)
  380. case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
  381. apiError(ctx, http.StatusForbidden, err)
  382. default:
  383. apiError(ctx, http.StatusInternalServerError, err)
  384. }
  385. return
  386. }
  387. ctx.Status(http.StatusCreated)
  388. }
  389. // DownloadRecipeFile serves the content of the requested recipe file
  390. func DownloadRecipeFile(ctx *context.Context) {
  391. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  392. downloadFile(ctx, recipeFileList, rref.AsKey())
  393. }
  394. // DownloadPackageFile serves the content of the requested package file
  395. func DownloadPackageFile(ctx *context.Context) {
  396. pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
  397. downloadFile(ctx, packageFileList, pref.AsKey())
  398. }
  399. func downloadFile(ctx *context.Context, fileFilter container.Set[string], fileKey string) {
  400. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  401. filename := ctx.PathParam("filename")
  402. if !fileFilter.Contains(filename) {
  403. apiError(ctx, http.StatusBadRequest, nil)
  404. return
  405. }
  406. s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
  407. ctx,
  408. &packages_service.PackageInfo{
  409. Owner: ctx.Package.Owner,
  410. PackageType: packages_model.TypeConan,
  411. Name: rref.Name,
  412. Version: rref.Version,
  413. },
  414. &packages_service.PackageFileInfo{
  415. Filename: filename,
  416. CompositeKey: fileKey,
  417. },
  418. ctx.Req.Method,
  419. )
  420. if err != nil {
  421. if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, packages_model.ErrPackageFileNotExist) {
  422. apiError(ctx, http.StatusNotFound, err)
  423. return
  424. }
  425. apiError(ctx, http.StatusInternalServerError, err)
  426. return
  427. }
  428. helper.ServePackageFile(ctx, s, u, pf)
  429. }
  430. // DeleteRecipeV1 deletes the requested recipe(s)
  431. func DeleteRecipeV1(ctx *context.Context) {
  432. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  433. if err := deleteRecipeOrPackage(ctx, rref, true, nil, false); err != nil {
  434. if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, conan_model.ErrPackageReferenceNotExist) {
  435. apiError(ctx, http.StatusNotFound, err)
  436. } else {
  437. apiError(ctx, http.StatusInternalServerError, err)
  438. }
  439. return
  440. }
  441. ctx.Status(http.StatusOK)
  442. }
  443. // DeleteRecipeV2 deletes the requested recipe(s) respecting its revisions
  444. func DeleteRecipeV2(ctx *context.Context) {
  445. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  446. if err := deleteRecipeOrPackage(ctx, rref, rref.Revision == "", nil, false); err != nil {
  447. if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, conan_model.ErrPackageReferenceNotExist) {
  448. apiError(ctx, http.StatusNotFound, err)
  449. } else {
  450. apiError(ctx, http.StatusInternalServerError, err)
  451. }
  452. return
  453. }
  454. ctx.Status(http.StatusOK)
  455. }
  456. // DeletePackageV1 deletes the requested package(s)
  457. func DeletePackageV1(ctx *context.Context) {
  458. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  459. type PackageReferences struct {
  460. References []string `json:"package_ids"`
  461. }
  462. var ids *PackageReferences
  463. if err := json.NewDecoder(ctx.Req.Body).Decode(&ids); err != nil {
  464. apiError(ctx, http.StatusInternalServerError, err)
  465. return
  466. }
  467. revisions, err := conan_model.GetRecipeRevisions(ctx, ctx.Package.Owner.ID, rref)
  468. if err != nil {
  469. apiError(ctx, http.StatusInternalServerError, err)
  470. return
  471. }
  472. for _, revision := range revisions {
  473. currentRref := rref.WithRevision(revision.Value)
  474. var references []*conan_model.PropertyValue
  475. if len(ids.References) == 0 {
  476. if references, err = conan_model.GetPackageReferences(ctx, ctx.Package.Owner.ID, currentRref); err != nil {
  477. apiError(ctx, http.StatusInternalServerError, err)
  478. return
  479. }
  480. } else {
  481. for _, reference := range ids.References {
  482. references = append(references, &conan_model.PropertyValue{Value: reference})
  483. }
  484. }
  485. for _, reference := range references {
  486. pref, _ := conan_module.NewPackageReference(currentRref, reference.Value, conan_module.DefaultRevision)
  487. if err := deleteRecipeOrPackage(ctx, currentRref, true, pref, true); err != nil {
  488. if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, conan_model.ErrPackageReferenceNotExist) {
  489. apiError(ctx, http.StatusNotFound, err)
  490. } else {
  491. apiError(ctx, http.StatusInternalServerError, err)
  492. }
  493. return
  494. }
  495. }
  496. }
  497. ctx.Status(http.StatusOK)
  498. }
  499. // DeletePackageV2 deletes the requested package(s) respecting its revisions
  500. func DeletePackageV2(ctx *context.Context) {
  501. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  502. pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
  503. if pref != nil { // has package reference
  504. if err := deleteRecipeOrPackage(ctx, rref, false, pref, pref.Revision == ""); err != nil {
  505. if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, conan_model.ErrPackageReferenceNotExist) {
  506. apiError(ctx, http.StatusNotFound, err)
  507. } else {
  508. apiError(ctx, http.StatusInternalServerError, err)
  509. }
  510. } else {
  511. ctx.Status(http.StatusOK)
  512. }
  513. return
  514. }
  515. references, err := conan_model.GetPackageReferences(ctx, ctx.Package.Owner.ID, rref)
  516. if err != nil {
  517. apiError(ctx, http.StatusInternalServerError, err)
  518. return
  519. }
  520. if len(references) == 0 {
  521. apiError(ctx, http.StatusNotFound, conan_model.ErrPackageReferenceNotExist)
  522. return
  523. }
  524. for _, reference := range references {
  525. pref, _ := conan_module.NewPackageReference(rref, reference.Value, conan_module.DefaultRevision)
  526. if err := deleteRecipeOrPackage(ctx, rref, false, pref, true); err != nil {
  527. if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, conan_model.ErrPackageReferenceNotExist) {
  528. apiError(ctx, http.StatusNotFound, err)
  529. } else {
  530. apiError(ctx, http.StatusInternalServerError, err)
  531. }
  532. return
  533. }
  534. }
  535. ctx.Status(http.StatusOK)
  536. }
  537. func deleteRecipeOrPackage(apictx *context.Context, rref *conan_module.RecipeReference, ignoreRecipeRevision bool, pref *conan_module.PackageReference, ignorePackageRevision bool) error {
  538. var pd *packages_model.PackageDescriptor
  539. versionDeleted := false
  540. err := db.WithTx(apictx, func(ctx std_ctx.Context) error {
  541. pv, err := packages_model.GetVersionByNameAndVersion(ctx, apictx.Package.Owner.ID, packages_model.TypeConan, rref.Name, rref.Version)
  542. if err != nil {
  543. return err
  544. }
  545. pd, err = packages_model.GetPackageDescriptor(ctx, pv)
  546. if err != nil {
  547. return err
  548. }
  549. filter := map[string]string{
  550. conan_module.PropertyRecipeUser: rref.User,
  551. conan_module.PropertyRecipeChannel: rref.Channel,
  552. }
  553. if !ignoreRecipeRevision {
  554. filter[conan_module.PropertyRecipeRevision] = rref.RevisionOrDefault()
  555. }
  556. if pref != nil {
  557. filter[conan_module.PropertyPackageReference] = pref.Reference
  558. if !ignorePackageRevision {
  559. filter[conan_module.PropertyPackageRevision] = pref.RevisionOrDefault()
  560. }
  561. }
  562. pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
  563. VersionID: pv.ID,
  564. Properties: filter,
  565. })
  566. if err != nil {
  567. return err
  568. }
  569. if len(pfs) == 0 {
  570. return conan_model.ErrPackageReferenceNotExist
  571. }
  572. for _, pf := range pfs {
  573. if err := packages_service.DeletePackageFile(ctx, pf); err != nil {
  574. return err
  575. }
  576. }
  577. has, err := packages_model.HasVersionFileReferences(ctx, pv.ID)
  578. if err != nil {
  579. return err
  580. }
  581. if !has {
  582. versionDeleted = true
  583. return packages_service.DeletePackageVersionAndReferences(ctx, pv)
  584. }
  585. return nil
  586. })
  587. if err != nil {
  588. return err
  589. }
  590. if versionDeleted {
  591. notify_service.PackageDelete(apictx, apictx.Doer, pd)
  592. }
  593. return nil
  594. }
  595. // ListRecipeRevisions gets a list of all recipe revisions
  596. func ListRecipeRevisions(ctx *context.Context) {
  597. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  598. revisions, err := conan_model.GetRecipeRevisions(ctx, ctx.Package.Owner.ID, rref)
  599. if err != nil {
  600. apiError(ctx, http.StatusInternalServerError, err)
  601. return
  602. }
  603. listRevisions(ctx, revisions)
  604. }
  605. // ListPackageRevisions gets a list of all package revisions
  606. func ListPackageRevisions(ctx *context.Context) {
  607. pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
  608. revisions, err := conan_model.GetPackageRevisions(ctx, ctx.Package.Owner.ID, pref)
  609. if err != nil {
  610. apiError(ctx, http.StatusInternalServerError, err)
  611. return
  612. }
  613. listRevisions(ctx, revisions)
  614. }
  615. type revisionInfo struct {
  616. Revision string `json:"revision"`
  617. Time time.Time `json:"time"`
  618. }
  619. func listRevisions(ctx *context.Context, revisions []*conan_model.PropertyValue) {
  620. if len(revisions) == 0 {
  621. apiError(ctx, http.StatusNotFound, conan_model.ErrRecipeReferenceNotExist)
  622. return
  623. }
  624. type RevisionList struct {
  625. Revisions []*revisionInfo `json:"revisions"`
  626. }
  627. revs := make([]*revisionInfo, 0, len(revisions))
  628. for _, rev := range revisions {
  629. revs = append(revs, &revisionInfo{Revision: rev.Value, Time: rev.CreatedUnix.AsLocalTime()})
  630. }
  631. jsonResponse(ctx, http.StatusOK, &RevisionList{revs})
  632. }
  633. // LatestRecipeRevision gets the latest recipe revision
  634. func LatestRecipeRevision(ctx *context.Context) {
  635. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  636. revision, err := conan_model.GetLastRecipeRevision(ctx, ctx.Package.Owner.ID, rref)
  637. if err != nil {
  638. if errors.Is(err, conan_model.ErrRecipeReferenceNotExist) || errors.Is(err, conan_model.ErrPackageReferenceNotExist) {
  639. apiError(ctx, http.StatusNotFound, err)
  640. } else {
  641. apiError(ctx, http.StatusInternalServerError, err)
  642. }
  643. return
  644. }
  645. jsonResponse(ctx, http.StatusOK, &revisionInfo{Revision: revision.Value, Time: revision.CreatedUnix.AsLocalTime()})
  646. }
  647. // LatestPackageRevision gets the latest package revision
  648. func LatestPackageRevision(ctx *context.Context) {
  649. pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
  650. revision, err := conan_model.GetLastPackageRevision(ctx, ctx.Package.Owner.ID, pref)
  651. if err != nil {
  652. if errors.Is(err, conan_model.ErrRecipeReferenceNotExist) || errors.Is(err, conan_model.ErrPackageReferenceNotExist) {
  653. apiError(ctx, http.StatusNotFound, err)
  654. } else {
  655. apiError(ctx, http.StatusInternalServerError, err)
  656. }
  657. return
  658. }
  659. jsonResponse(ctx, http.StatusOK, &revisionInfo{Revision: revision.Value, Time: revision.CreatedUnix.AsLocalTime()})
  660. }
  661. // ListRecipeRevisionFiles gets a list of all recipe revision files
  662. func ListRecipeRevisionFiles(ctx *context.Context) {
  663. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  664. listRevisionFiles(ctx, rref.AsKey())
  665. }
  666. // ListPackageRevisionFiles gets a list of all package revision files
  667. func ListPackageRevisionFiles(ctx *context.Context) {
  668. pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
  669. listRevisionFiles(ctx, pref.AsKey())
  670. }
  671. func listRevisionFiles(ctx *context.Context, fileKey string) {
  672. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  673. pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeConan, rref.Name, rref.Version)
  674. if err != nil {
  675. if errors.Is(err, packages_model.ErrPackageNotExist) {
  676. apiError(ctx, http.StatusNotFound, err)
  677. } else {
  678. apiError(ctx, http.StatusInternalServerError, err)
  679. }
  680. return
  681. }
  682. pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
  683. VersionID: pv.ID,
  684. CompositeKey: fileKey,
  685. })
  686. if err != nil {
  687. apiError(ctx, http.StatusInternalServerError, err)
  688. return
  689. }
  690. if len(pfs) == 0 {
  691. apiError(ctx, http.StatusNotFound, nil)
  692. return
  693. }
  694. files := make(map[string]any)
  695. for _, pf := range pfs {
  696. files[pf.Name] = nil
  697. }
  698. type FileList struct {
  699. Files map[string]any `json:"files"`
  700. }
  701. jsonResponse(ctx, http.StatusOK, &FileList{
  702. Files: files,
  703. })
  704. }