code.gitea.io/gitea@v1.21.7/routers/api/packages/nuget/nuget.go (about) 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package nuget 5 6 import ( 7 "encoding/xml" 8 "errors" 9 "fmt" 10 "io" 11 "net/http" 12 "net/url" 13 "regexp" 14 "strconv" 15 "strings" 16 17 "code.gitea.io/gitea/models/db" 18 packages_model "code.gitea.io/gitea/models/packages" 19 nuget_model "code.gitea.io/gitea/models/packages/nuget" 20 "code.gitea.io/gitea/modules/context" 21 "code.gitea.io/gitea/modules/log" 22 packages_module "code.gitea.io/gitea/modules/packages" 23 nuget_module "code.gitea.io/gitea/modules/packages/nuget" 24 "code.gitea.io/gitea/modules/setting" 25 "code.gitea.io/gitea/modules/util" 26 "code.gitea.io/gitea/routers/api/packages/helper" 27 packages_service "code.gitea.io/gitea/services/packages" 28 ) 29 30 func apiError(ctx *context.Context, status int, obj any) { 31 helper.LogAndProcessError(ctx, status, obj, func(message string) { 32 ctx.JSON(status, map[string]string{ 33 "Message": message, 34 }) 35 }) 36 } 37 38 func xmlResponse(ctx *context.Context, status int, obj any) { 39 ctx.Resp.Header().Set("Content-Type", "application/atom+xml; charset=utf-8") 40 ctx.Resp.WriteHeader(status) 41 if _, err := ctx.Resp.Write([]byte(xml.Header)); err != nil { 42 log.Error("Write failed: %v", err) 43 } 44 if err := xml.NewEncoder(ctx.Resp).Encode(obj); err != nil { 45 log.Error("XML encode failed: %v", err) 46 } 47 } 48 49 // https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs 50 func ServiceIndexV2(ctx *context.Context) { 51 base := setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget" 52 53 xmlResponse(ctx, http.StatusOK, &ServiceIndexResponseV2{ 54 Base: base, 55 Xmlns: "http://www.w3.org/2007/app", 56 XmlnsAtom: "http://www.w3.org/2005/Atom", 57 Workspace: ServiceWorkspace{ 58 Title: AtomTitle{ 59 Type: "text", 60 Text: "Default", 61 }, 62 Collection: ServiceCollection{ 63 Href: "Packages", 64 Title: AtomTitle{ 65 Type: "text", 66 Text: "Packages", 67 }, 68 }, 69 }, 70 }) 71 } 72 73 // https://docs.microsoft.com/en-us/nuget/api/service-index 74 func ServiceIndexV3(ctx *context.Context) { 75 root := setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget" 76 77 ctx.JSON(http.StatusOK, &ServiceIndexResponseV3{ 78 Version: "3.0.0", 79 Resources: []ServiceResource{ 80 {ID: root + "/query", Type: "SearchQueryService"}, 81 {ID: root + "/query", Type: "SearchQueryService/3.0.0-beta"}, 82 {ID: root + "/query", Type: "SearchQueryService/3.0.0-rc"}, 83 {ID: root + "/registration", Type: "RegistrationsBaseUrl"}, 84 {ID: root + "/registration", Type: "RegistrationsBaseUrl/3.0.0-beta"}, 85 {ID: root + "/registration", Type: "RegistrationsBaseUrl/3.0.0-rc"}, 86 {ID: root + "/package", Type: "PackageBaseAddress/3.0.0"}, 87 {ID: root, Type: "PackagePublish/2.0.0"}, 88 {ID: root + "/symbolpackage", Type: "SymbolPackagePublish/4.9.0"}, 89 }, 90 }) 91 } 92 93 // https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/LegacyFeedCapabilityResourceV2Feed.cs 94 func FeedCapabilityResource(ctx *context.Context) { 95 xmlResponse(ctx, http.StatusOK, Metadata) 96 } 97 98 var searchTermExtract = regexp.MustCompile(`'([^']+)'`) 99 100 func getSearchTerm(ctx *context.Context) string { 101 searchTerm := strings.Trim(ctx.FormTrim("searchTerm"), "'") 102 if searchTerm == "" { 103 // $filter contains a query like: 104 // (((Id ne null) and substringof('microsoft',tolower(Id))) 105 // We don't support these queries, just extract the search term. 106 match := searchTermExtract.FindStringSubmatch(ctx.FormTrim("$filter")) 107 if len(match) == 2 { 108 searchTerm = strings.TrimSpace(match[1]) 109 } 110 } 111 return searchTerm 112 } 113 114 // https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs 115 func SearchServiceV2(ctx *context.Context) { 116 skip, take := ctx.FormInt("$skip"), ctx.FormInt("$top") 117 paginator := db.NewAbsoluteListOptions(skip, take) 118 119 pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{ 120 OwnerID: ctx.Package.Owner.ID, 121 Type: packages_model.TypeNuGet, 122 Name: packages_model.SearchValue{ 123 Value: getSearchTerm(ctx), 124 }, 125 IsInternal: util.OptionalBoolFalse, 126 Paginator: paginator, 127 }) 128 if err != nil { 129 apiError(ctx, http.StatusInternalServerError, err) 130 return 131 } 132 133 pds, err := packages_model.GetPackageDescriptors(ctx, pvs) 134 if err != nil { 135 apiError(ctx, http.StatusInternalServerError, err) 136 return 137 } 138 139 skip, take = paginator.GetSkipTake() 140 141 var next *nextOptions 142 if len(pvs) == take { 143 next = &nextOptions{ 144 Path: "Search()", 145 Query: url.Values{}, 146 } 147 searchTerm := ctx.FormTrim("searchTerm") 148 if searchTerm != "" { 149 next.Query.Set("searchTerm", searchTerm) 150 } 151 filter := ctx.FormTrim("$filter") 152 if filter != "" { 153 next.Query.Set("$filter", filter) 154 } 155 next.Query.Set("$skip", strconv.Itoa(skip+take)) 156 next.Query.Set("$top", strconv.Itoa(take)) 157 } 158 159 resp := createFeedResponse( 160 &linkBuilder{Base: setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget", Next: next}, 161 total, 162 pds, 163 ) 164 165 xmlResponse(ctx, http.StatusOK, resp) 166 } 167 168 // http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part2-url-conventions/odata-v4.0-errata03-os-part2-url-conventions-complete.html#_Toc453752351 169 func SearchServiceV2Count(ctx *context.Context) { 170 count, err := nuget_model.CountPackages(ctx, &packages_model.PackageSearchOptions{ 171 OwnerID: ctx.Package.Owner.ID, 172 Name: packages_model.SearchValue{ 173 Value: getSearchTerm(ctx), 174 }, 175 IsInternal: util.OptionalBoolFalse, 176 }) 177 if err != nil { 178 apiError(ctx, http.StatusInternalServerError, err) 179 return 180 } 181 182 ctx.PlainText(http.StatusOK, strconv.FormatInt(count, 10)) 183 } 184 185 // https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-for-packages 186 func SearchServiceV3(ctx *context.Context) { 187 pvs, count, err := nuget_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ 188 OwnerID: ctx.Package.Owner.ID, 189 Name: packages_model.SearchValue{Value: ctx.FormTrim("q")}, 190 IsInternal: util.OptionalBoolFalse, 191 Paginator: db.NewAbsoluteListOptions( 192 ctx.FormInt("skip"), 193 ctx.FormInt("take"), 194 ), 195 }) 196 if err != nil { 197 apiError(ctx, http.StatusInternalServerError, err) 198 return 199 } 200 201 pds, err := packages_model.GetPackageDescriptors(ctx, pvs) 202 if err != nil { 203 apiError(ctx, http.StatusInternalServerError, err) 204 return 205 } 206 207 resp := createSearchResultResponse( 208 &linkBuilder{Base: setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"}, 209 count, 210 pds, 211 ) 212 213 ctx.JSON(http.StatusOK, resp) 214 } 215 216 // https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#registration-index 217 func RegistrationIndex(ctx *context.Context) { 218 packageName := ctx.Params("id") 219 220 pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName) 221 if err != nil { 222 apiError(ctx, http.StatusInternalServerError, err) 223 return 224 } 225 if len(pvs) == 0 { 226 apiError(ctx, http.StatusNotFound, err) 227 return 228 } 229 230 pds, err := packages_model.GetPackageDescriptors(ctx, pvs) 231 if err != nil { 232 apiError(ctx, http.StatusInternalServerError, err) 233 return 234 } 235 236 resp := createRegistrationIndexResponse( 237 &linkBuilder{Base: setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"}, 238 pds, 239 ) 240 241 ctx.JSON(http.StatusOK, resp) 242 } 243 244 // https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs 245 func RegistrationLeafV2(ctx *context.Context) { 246 packageName := ctx.Params("id") 247 packageVersion := ctx.Params("version") 248 249 pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName, packageVersion) 250 if err != nil { 251 if err == packages_model.ErrPackageNotExist { 252 apiError(ctx, http.StatusNotFound, err) 253 return 254 } 255 apiError(ctx, http.StatusInternalServerError, err) 256 return 257 } 258 259 pd, err := packages_model.GetPackageDescriptor(ctx, pv) 260 if err != nil { 261 apiError(ctx, http.StatusInternalServerError, err) 262 return 263 } 264 265 resp := createEntryResponse( 266 &linkBuilder{Base: setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"}, 267 pd, 268 ) 269 270 xmlResponse(ctx, http.StatusOK, resp) 271 } 272 273 // https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#registration-leaf 274 func RegistrationLeafV3(ctx *context.Context) { 275 packageName := ctx.Params("id") 276 packageVersion := strings.TrimSuffix(ctx.Params("version"), ".json") 277 278 pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName, packageVersion) 279 if err != nil { 280 if err == packages_model.ErrPackageNotExist { 281 apiError(ctx, http.StatusNotFound, err) 282 return 283 } 284 apiError(ctx, http.StatusInternalServerError, err) 285 return 286 } 287 288 pd, err := packages_model.GetPackageDescriptor(ctx, pv) 289 if err != nil { 290 apiError(ctx, http.StatusInternalServerError, err) 291 return 292 } 293 294 resp := createRegistrationLeafResponse( 295 &linkBuilder{Base: setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"}, 296 pd, 297 ) 298 299 ctx.JSON(http.StatusOK, resp) 300 } 301 302 // https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs 303 func EnumeratePackageVersionsV2(ctx *context.Context) { 304 packageName := strings.Trim(ctx.FormTrim("id"), "'") 305 306 skip, take := ctx.FormInt("$skip"), ctx.FormInt("$top") 307 paginator := db.NewAbsoluteListOptions(skip, take) 308 309 pvs, total, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ 310 OwnerID: ctx.Package.Owner.ID, 311 Type: packages_model.TypeNuGet, 312 Name: packages_model.SearchValue{ 313 ExactMatch: true, 314 Value: packageName, 315 }, 316 IsInternal: util.OptionalBoolFalse, 317 Paginator: paginator, 318 }) 319 if err != nil { 320 apiError(ctx, http.StatusInternalServerError, err) 321 return 322 } 323 324 pds, err := packages_model.GetPackageDescriptors(ctx, pvs) 325 if err != nil { 326 apiError(ctx, http.StatusInternalServerError, err) 327 return 328 } 329 330 skip, take = paginator.GetSkipTake() 331 332 var next *nextOptions 333 if len(pvs) == take { 334 next = &nextOptions{ 335 Path: "FindPackagesById()", 336 Query: url.Values{}, 337 } 338 next.Query.Set("id", packageName) 339 next.Query.Set("$skip", strconv.Itoa(skip+take)) 340 next.Query.Set("$top", strconv.Itoa(take)) 341 } 342 343 resp := createFeedResponse( 344 &linkBuilder{Base: setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget", Next: next}, 345 total, 346 pds, 347 ) 348 349 xmlResponse(ctx, http.StatusOK, resp) 350 } 351 352 // http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part2-url-conventions/odata-v4.0-errata03-os-part2-url-conventions-complete.html#_Toc453752351 353 func EnumeratePackageVersionsV2Count(ctx *context.Context) { 354 count, err := packages_model.CountVersions(ctx, &packages_model.PackageSearchOptions{ 355 OwnerID: ctx.Package.Owner.ID, 356 Type: packages_model.TypeNuGet, 357 Name: packages_model.SearchValue{ 358 ExactMatch: true, 359 Value: strings.Trim(ctx.FormTrim("id"), "'"), 360 }, 361 IsInternal: util.OptionalBoolFalse, 362 }) 363 if err != nil { 364 apiError(ctx, http.StatusInternalServerError, err) 365 return 366 } 367 368 ctx.PlainText(http.StatusOK, strconv.FormatInt(count, 10)) 369 } 370 371 // https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource#enumerate-package-versions 372 func EnumeratePackageVersionsV3(ctx *context.Context) { 373 packageName := ctx.Params("id") 374 375 pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName) 376 if err != nil { 377 apiError(ctx, http.StatusInternalServerError, err) 378 return 379 } 380 if len(pvs) == 0 { 381 apiError(ctx, http.StatusNotFound, err) 382 return 383 } 384 385 resp := createPackageVersionsResponse(pvs) 386 387 ctx.JSON(http.StatusOK, resp) 388 } 389 390 // https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource#download-package-content-nupkg 391 func DownloadPackageFile(ctx *context.Context) { 392 packageName := ctx.Params("id") 393 packageVersion := ctx.Params("version") 394 filename := ctx.Params("filename") 395 396 s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 397 ctx, 398 &packages_service.PackageInfo{ 399 Owner: ctx.Package.Owner, 400 PackageType: packages_model.TypeNuGet, 401 Name: packageName, 402 Version: packageVersion, 403 }, 404 &packages_service.PackageFileInfo{ 405 Filename: filename, 406 }, 407 ) 408 if err != nil { 409 if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { 410 apiError(ctx, http.StatusNotFound, err) 411 return 412 } 413 apiError(ctx, http.StatusInternalServerError, err) 414 return 415 } 416 417 helper.ServePackageFile(ctx, s, u, pf) 418 } 419 420 // UploadPackage creates a new package with the metadata contained in the uploaded nupgk file 421 // https://docs.microsoft.com/en-us/nuget/api/package-publish-resource#push-a-package 422 func UploadPackage(ctx *context.Context) { 423 np, buf, closables := processUploadedFile(ctx, nuget_module.DependencyPackage) 424 defer func() { 425 for _, c := range closables { 426 c.Close() 427 } 428 }() 429 if np == nil { 430 return 431 } 432 433 _, _, err := packages_service.CreatePackageAndAddFile( 434 ctx, 435 &packages_service.PackageCreationInfo{ 436 PackageInfo: packages_service.PackageInfo{ 437 Owner: ctx.Package.Owner, 438 PackageType: packages_model.TypeNuGet, 439 Name: np.ID, 440 Version: np.Version, 441 }, 442 SemverCompatible: true, 443 Creator: ctx.Doer, 444 Metadata: np.Metadata, 445 }, 446 &packages_service.PackageFileCreationInfo{ 447 PackageFileInfo: packages_service.PackageFileInfo{ 448 Filename: strings.ToLower(fmt.Sprintf("%s.%s.nupkg", np.ID, np.Version)), 449 }, 450 Creator: ctx.Doer, 451 Data: buf, 452 IsLead: true, 453 }, 454 ) 455 if err != nil { 456 switch err { 457 case packages_model.ErrDuplicatePackageVersion: 458 apiError(ctx, http.StatusConflict, err) 459 case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: 460 apiError(ctx, http.StatusForbidden, err) 461 default: 462 apiError(ctx, http.StatusInternalServerError, err) 463 } 464 return 465 } 466 467 ctx.Status(http.StatusCreated) 468 } 469 470 // UploadSymbolPackage adds a symbol package to an existing package 471 // https://docs.microsoft.com/en-us/nuget/api/symbol-package-publish-resource 472 func UploadSymbolPackage(ctx *context.Context) { 473 np, buf, closables := processUploadedFile(ctx, nuget_module.SymbolsPackage) 474 defer func() { 475 for _, c := range closables { 476 c.Close() 477 } 478 }() 479 if np == nil { 480 return 481 } 482 483 pdbs, err := nuget_module.ExtractPortablePdb(buf, buf.Size()) 484 if err != nil { 485 if errors.Is(err, util.ErrInvalidArgument) { 486 apiError(ctx, http.StatusBadRequest, err) 487 } else { 488 apiError(ctx, http.StatusInternalServerError, err) 489 } 490 return 491 } 492 defer pdbs.Close() 493 494 if _, err := buf.Seek(0, io.SeekStart); err != nil { 495 apiError(ctx, http.StatusInternalServerError, err) 496 return 497 } 498 499 pi := &packages_service.PackageInfo{ 500 Owner: ctx.Package.Owner, 501 PackageType: packages_model.TypeNuGet, 502 Name: np.ID, 503 Version: np.Version, 504 } 505 506 _, err = packages_service.AddFileToExistingPackage( 507 ctx, 508 pi, 509 &packages_service.PackageFileCreationInfo{ 510 PackageFileInfo: packages_service.PackageFileInfo{ 511 Filename: strings.ToLower(fmt.Sprintf("%s.%s.snupkg", np.ID, np.Version)), 512 }, 513 Creator: ctx.Doer, 514 Data: buf, 515 IsLead: false, 516 }, 517 ) 518 if err != nil { 519 switch err { 520 case packages_model.ErrPackageNotExist: 521 apiError(ctx, http.StatusNotFound, err) 522 case packages_model.ErrDuplicatePackageFile: 523 apiError(ctx, http.StatusConflict, err) 524 case packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: 525 apiError(ctx, http.StatusForbidden, err) 526 default: 527 apiError(ctx, http.StatusInternalServerError, err) 528 } 529 return 530 } 531 532 for _, pdb := range pdbs { 533 _, err := packages_service.AddFileToExistingPackage( 534 ctx, 535 pi, 536 &packages_service.PackageFileCreationInfo{ 537 PackageFileInfo: packages_service.PackageFileInfo{ 538 Filename: strings.ToLower(pdb.Name), 539 CompositeKey: strings.ToLower(pdb.ID), 540 }, 541 Creator: ctx.Doer, 542 Data: pdb.Content, 543 IsLead: false, 544 Properties: map[string]string{ 545 nuget_module.PropertySymbolID: strings.ToLower(pdb.ID), 546 }, 547 }, 548 ) 549 if err != nil { 550 switch err { 551 case packages_model.ErrDuplicatePackageFile: 552 apiError(ctx, http.StatusConflict, err) 553 case packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: 554 apiError(ctx, http.StatusForbidden, err) 555 default: 556 apiError(ctx, http.StatusInternalServerError, err) 557 } 558 return 559 } 560 } 561 562 ctx.Status(http.StatusCreated) 563 } 564 565 func processUploadedFile(ctx *context.Context, expectedType nuget_module.PackageType) (*nuget_module.Package, *packages_module.HashedBuffer, []io.Closer) { 566 closables := make([]io.Closer, 0, 2) 567 568 upload, close, err := ctx.UploadStream() 569 if err != nil { 570 apiError(ctx, http.StatusBadRequest, err) 571 return nil, nil, closables 572 } 573 574 if close { 575 closables = append(closables, upload) 576 } 577 578 buf, err := packages_module.CreateHashedBufferFromReader(upload) 579 if err != nil { 580 apiError(ctx, http.StatusInternalServerError, err) 581 return nil, nil, closables 582 } 583 closables = append(closables, buf) 584 585 np, err := nuget_module.ParsePackageMetaData(buf, buf.Size()) 586 if err != nil { 587 if errors.Is(err, util.ErrInvalidArgument) { 588 apiError(ctx, http.StatusBadRequest, err) 589 } else { 590 apiError(ctx, http.StatusInternalServerError, err) 591 } 592 return nil, nil, closables 593 } 594 if np.PackageType != expectedType { 595 apiError(ctx, http.StatusBadRequest, errors.New("unexpected package type")) 596 return nil, nil, closables 597 } 598 if _, err := buf.Seek(0, io.SeekStart); err != nil { 599 apiError(ctx, http.StatusInternalServerError, err) 600 return nil, nil, closables 601 } 602 return np, buf, closables 603 } 604 605 // https://github.com/dotnet/symstore/blob/main/docs/specs/Simple_Symbol_Query_Protocol.md#request 606 func DownloadSymbolFile(ctx *context.Context) { 607 filename := ctx.Params("filename") 608 guid := ctx.Params("guid")[:32] 609 filename2 := ctx.Params("filename2") 610 611 if filename != filename2 { 612 apiError(ctx, http.StatusBadRequest, nil) 613 return 614 } 615 616 pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{ 617 OwnerID: ctx.Package.Owner.ID, 618 PackageType: packages_model.TypeNuGet, 619 Query: filename, 620 Properties: map[string]string{ 621 nuget_module.PropertySymbolID: strings.ToLower(guid), 622 }, 623 }) 624 if err != nil { 625 apiError(ctx, http.StatusInternalServerError, err) 626 return 627 } 628 if len(pfs) != 1 { 629 apiError(ctx, http.StatusNotFound, nil) 630 return 631 } 632 633 s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0]) 634 if err != nil { 635 if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { 636 apiError(ctx, http.StatusNotFound, err) 637 return 638 } 639 apiError(ctx, http.StatusInternalServerError, err) 640 return 641 } 642 643 helper.ServePackageFile(ctx, s, u, pf) 644 } 645 646 // DeletePackage hard deletes the package 647 // https://docs.microsoft.com/en-us/nuget/api/package-publish-resource#delete-a-package 648 func DeletePackage(ctx *context.Context) { 649 packageName := ctx.Params("id") 650 packageVersion := ctx.Params("version") 651 652 err := packages_service.RemovePackageVersionByNameAndVersion( 653 ctx, 654 ctx.Doer, 655 &packages_service.PackageInfo{ 656 Owner: ctx.Package.Owner, 657 PackageType: packages_model.TypeNuGet, 658 Name: packageName, 659 Version: packageVersion, 660 }, 661 ) 662 if err != nil { 663 if err == packages_model.ErrPackageNotExist { 664 apiError(ctx, http.StatusNotFound, err) 665 return 666 } 667 apiError(ctx, http.StatusInternalServerError, err) 668 } 669 670 ctx.Status(http.StatusNoContent) 671 }