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