Skip to content

Commit ff551d2

Browse files
committed
add API end-points for indexing
Signed-off-by: Varsha U N <varshaun58@gmail.com>
1 parent d9875ff commit ff551d2

4 files changed

Lines changed: 198 additions & 0 deletions

File tree

scanpipe/api/serializers.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from scanpipe.models import CodebaseResource
3333
from scanpipe.models import DiscoveredDependency
3434
from scanpipe.models import DiscoveredPackage
35+
from scanpipe.models import DownloadedPackage
3536
from scanpipe.models import InputSource
3637
from scanpipe.models import Project
3738
from scanpipe.models import ProjectMessage
@@ -520,6 +521,37 @@ class Meta:
520521
]
521522

522523

524+
class DownloadedPackageSerializer(serializers.ModelSerializer):
525+
download_url = serializers.SerializerMethodField()
526+
archive_checksum = serializers.CharField(
527+
source="package_archive.checksum_sha256", read_only=True
528+
)
529+
530+
class Meta:
531+
model = DownloadedPackage
532+
fields = [
533+
"id",
534+
"url",
535+
"filename",
536+
"download_date",
537+
"scan_log",
538+
"scan_date",
539+
"scancode_version",
540+
"pipeline_name",
541+
"archive_checksum",
542+
"download_url",
543+
]
544+
read_only_fields = fields
545+
546+
def get_download_url(self, obj):
547+
request = self.context.get("request")
548+
if obj.package_archive.package_file and request:
549+
return request.build_absolute_uri(
550+
f"/api/projects/{obj.project.uuid}/packages/{obj.id}/download/"
551+
)
552+
return None
553+
554+
523555
def get_model_serializer(model_class):
524556
"""Return a Serializer class that ia related to a given `model_class`."""
525557
serializer = {
@@ -528,6 +560,7 @@ def get_model_serializer(model_class):
528560
DiscoveredDependency: DiscoveredDependencySerializer,
529561
CodebaseRelation: CodebaseRelationSerializer,
530562
ProjectMessage: ProjectMessageSerializer,
563+
DownloadedPackage: DownloadedPackageSerializer,
531564
}.get(model_class, None)
532565

533566
if not serializer:

scanpipe/api/urls.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# scanpipe/api/urls.py
2+
# SPDX-License-Identifier: Apache-2.0
3+
#
4+
# http://nexb.com and https://github.com/aboutcode-org/scancode.io
5+
# The ScanCode.io software is licensed under the Apache License version 2.0.
6+
# Data generated with ScanCode.io is provided as-is without warranties.
7+
# ScanCode is a trademark of nexB Inc.
8+
#
9+
# You may not use this software except in compliance with the License.
10+
# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0
11+
# Unless required by applicable law or agreed to in writing, software distributed
12+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
13+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
14+
# specific language governing permissions and limitations under the License.
15+
#
16+
# Data Generated with ScanCode.io is provided on an "AS IS" BASIS, WITHOUT WARRANTIES
17+
# OR CONDITIONS OF ANY KIND, either express or implied. No content created from
18+
# ScanCode.io should be considered or used as legal advice. Consult an Attorney
19+
# for any legal advice.
20+
#
21+
# ScanCode.io is a free software code scanning tool from nexB Inc. and others.
22+
# Visit https://github.com/aboutcode-org/scancode.io for support and download.
23+
24+
from rest_framework.routers import DefaultRouter
25+
26+
from scanpipe.api.views import DownloadedPackageViewSet
27+
28+
router = DefaultRouter()
29+
router.register(
30+
r"projects/(?P<project_uuid>[^/.]+)/packages",
31+
DownloadedPackageViewSet,
32+
basename="downloaded-package",
33+
)
34+
35+
urlpatterns = router.urls

scanpipe/api/views.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
# Visit https://github.com/aboutcode-org/scancode.io for support and download.
2222

2323
import json
24+
import os
2425

2526
from django.apps import apps
2627
from django.core.exceptions import ObjectDoesNotExist
@@ -34,12 +35,14 @@
3435
from rest_framework import status
3536
from rest_framework import viewsets
3637
from rest_framework.decorators import action
38+
from rest_framework.permissions import IsAuthenticatedOrReadOnly
3739
from rest_framework.response import Response
3840

3941
from scanpipe.api.serializers import CodebaseRelationSerializer
4042
from scanpipe.api.serializers import CodebaseResourceSerializer
4143
from scanpipe.api.serializers import DiscoveredDependencySerializer
4244
from scanpipe.api.serializers import DiscoveredPackageSerializer
45+
from scanpipe.api.serializers import DownloadedPackageSerializer
4346
from scanpipe.api.serializers import PipelineSerializer
4447
from scanpipe.api.serializers import ProjectMessageSerializer
4548
from scanpipe.api.serializers import ProjectSerializer
@@ -50,6 +53,7 @@
5053
from scanpipe.filters import ProjectMessageFilterSet
5154
from scanpipe.filters import RelationFilterSet
5255
from scanpipe.filters import ResourceFilterSet
56+
from scanpipe.models import DownloadedPackage
5357
from scanpipe.models import Project
5458
from scanpipe.models import Run
5559
from scanpipe.models import RunInProgressError
@@ -526,3 +530,123 @@ def delete_pipeline(self, request, *args, **kwargs):
526530

527531
run.delete_task()
528532
return Response({"status": f"Pipeline {run.pipeline_name} deleted."})
533+
534+
535+
class DownloadedPackageFilter(django_filters.FilterSet):
536+
url = django_filters.CharFilter(lookup_expr="exact")
537+
checksum = django_filters.CharFilter(
538+
field_name="package_archive__checksum_sha256", lookup_expr="exact"
539+
)
540+
541+
class Meta:
542+
model = DownloadedPackage
543+
fields = ["url", "checksum"]
544+
545+
546+
class DownloadedPackageViewSet(
547+
mixins.ListModelMixin,
548+
mixins.RetrieveModelMixin,
549+
viewsets.GenericViewSet,
550+
):
551+
"""
552+
A viewset for managing DownloadedPackage instances, providing endpoints to list,
553+
retrieve, and download packages by ID, URL, or checksum.
554+
"""
555+
556+
queryset = DownloadedPackage.objects.select_related("package_archive")
557+
serializer_class = DownloadedPackageSerializer
558+
permission_classes = [IsAuthenticatedOrReadOnly]
559+
lookup_field = "id"
560+
filterset_class = DownloadedPackageFilter
561+
filter_backends = [django_filters.rest_framework.DjangoFilterBackend]
562+
563+
def get_queryset(self):
564+
project_uuid = self.kwargs["project_uuid"]
565+
try:
566+
project = Project.objects.get(uuid=project_uuid)
567+
return self.queryset.filter(project=project)
568+
except Project.DoesNotExist:
569+
return self.queryset.none()
570+
571+
@action(detail=True, methods=["get"])
572+
def download(self, request, project_uuid=None, id=None):
573+
"""Download a package file by ID."""
574+
try:
575+
package = self.get_queryset().get(id=id)
576+
file_path = package.package_archive.package_file.path
577+
if not file_path or not os.path.exists(file_path):
578+
return Response({"error": "Package file not found"}, status=404)
579+
file_name = os.path.basename(file_path)
580+
return FileResponse(
581+
open(file_path, "rb"),
582+
as_attachment=True,
583+
filename=file_name,
584+
)
585+
except DownloadedPackage.DoesNotExist:
586+
return Response({"error": "Package not found"}, status=404)
587+
588+
@action(detail=False, methods=["get"])
589+
def by_url(self, request, project_uuid=None):
590+
"""Query package details by URL."""
591+
url = request.query_params.get("url")
592+
if not url:
593+
return Response({"error": "URL parameter is required"}, status=400)
594+
try:
595+
package = self.get_queryset().get(url=url)
596+
serializer = self.get_serializer(package)
597+
return Response(serializer.data)
598+
except DownloadedPackage.DoesNotExist:
599+
return Response({"error": "Package not found"}, status=404)
600+
601+
@action(detail=False, methods=["get"])
602+
def download_by_url(self, request, project_uuid=None):
603+
"""Download a package file by URL."""
604+
url = request.query_params.get("url")
605+
if not url:
606+
return Response({"error": "URL parameter is required"}, status=400)
607+
try:
608+
package = self.get_queryset().get(url=url)
609+
file_path = package.package_archive.package_file.path
610+
if not file_path or not os.path.exists(file_path):
611+
return Response({"error": "Package file not found"}, status=404)
612+
file_name = os.path.basename(file_path)
613+
return FileResponse(
614+
open(file_path, "rb"),
615+
as_attachment=True,
616+
filename=file_name,
617+
)
618+
except DownloadedPackage.DoesNotExist:
619+
return Response({"error": "Package not found"}, status=404)
620+
621+
@action(detail=False, methods=["get"])
622+
def by_checksum(self, request, project_uuid=None):
623+
"""Query package details by SHA256 checksum."""
624+
checksum = request.query_params.get("checksum")
625+
if not checksum:
626+
return Response({"error": "Checksum parameter is required"}, status=400)
627+
try:
628+
package = self.get_queryset().get(package_archive__checksum_sha256=checksum)
629+
serializer = self.get_serializer(package)
630+
return Response(serializer.data)
631+
except DownloadedPackage.DoesNotExist:
632+
return Response({"error": "Package not found"}, status=404)
633+
634+
@action(detail=False, methods=["get"])
635+
def download_by_checksum(self, request, project_uuid=None):
636+
"""Download a package file by SHA256 checksum."""
637+
checksum = request.query_params.get("checksum")
638+
if not checksum:
639+
return Response({"error": "Checksum parameter is required"}, status=400)
640+
try:
641+
package = self.get_queryset().get(package_archive__checksum_sha256=checksum)
642+
file_path = package.package_archive.package_file.path
643+
if not file_path or not os.path.exists(file_path):
644+
return Response({"error": "Package file not found"}, status=404)
645+
file_name = os.path.basename(file_path)
646+
return FileResponse(
647+
open(file_path, "rb"),
648+
as_attachment=True,
649+
filename=file_name,
650+
)
651+
except DownloadedPackage.DoesNotExist:
652+
return Response({"error": "Package not found"}, status=404)

scanpipe/urls.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,4 +242,10 @@
242242
name="license_list",
243243
),
244244
path("monitor/", include("django_rq.urls")),
245+
path(
246+
"project/<slug:slug>/", views.ProjectDetailView.as_view(), name="project_detail"
247+
),
248+
path("license/", views.LicenseListView.as_view(), name="license_list"),
249+
path("monitor/", include("django_rq.urls")),
250+
path("api/", include("scanpipe.api.urls")),
245251
]

0 commit comments

Comments
 (0)