Skip to content

feat: added and modified api routes for optimization reports #1079

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 22 commits into
base: main
Choose a base branch
from

Conversation

ssilare-adobe
Copy link
Contributor

@ssilare-adobe ssilare-adobe commented Jul 21, 2025

Reports API Endpoints

JIRA - SITES-33657

Overview

The Reports API allows you to create, manage, and retrieve report generation jobs for sites. All endpoints require authentication and organization-level access.

Authentication

Include one of the following headers:

  • Authorization: Bearer <jwt-token>
  • Authorization: Bearer <ims-token>
  • x-api-key: <api-key>

Endpoints

Method Endpoint Description Request Body Response
POST /sites/{id}/reports Creates a report job {reportType, reportPeriod, comparisonPeriod} {jobId, status}
GET /sites/{id}/reports Lists all reports - {reports: [...]}
GET /sites/{id}/reports/{reportId} Gets one report - {report details + presigned URLs}
DELETE /sites/{id}/reports/{reportId} Deletes a report and S3 files - {message}

Request Examples

POST /sites/{siteId}/reports

Request:

curl -X POST "https://api.spacecat.com/sites/123e4567-e89b-12d3-a456-426614174000/reports" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{
    "reportType": "performance",
    "reportPeriod": {
      "startDate": "2025-01-01",
      "endDate": "2025-01-31"
    },
    "comparisonPeriod": {
      "startDate": "2024-12-01", 
      "endDate": "2024-12-31"
    }
  }'

Response:

{
  "message": "Report generation job queued successfully",
  "siteId": "123e4567-e89b-12d3-a456-426614174000",
  "reportType": "performance",
  "status": "processing",
  "jobId": "987e6543-e21b-12d3-a456-426614174001",
  "timestamp": "2025-01-15T10:00:00Z"
}
Untitled diagram _ Mermaid Chart-2025-08-04-095151

GET /sites/{siteId}/reports

Request:

curl -X GET "https://api.spacecat.com/sites/123e4567-e89b-12d3-a456-426614174000/reports" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response:

{
  "siteId": "123e4567-e89b-12d3-a456-426614174000",
  "reports": [
    {
      "id": "987e6543-e21b-12d3-a456-426614174001",
      "siteId": "123e4567-e89b-12d3-a456-426614174000",
      "reportType": "performance",
      "status": "success",
      "reportPeriod": {
        "startDate": "2025-01-01",
        "endDate": "2025-01-31"
      },
      "comparisonPeriod": {
        "startDate": "2024-12-01",
        "endDate": "2024-12-31"
      },
      "storagePath": "/reports/123e4567-e89b-12d3-a456-426614174000/performance/987e6543-e21b-12d3-a456-426614174001/",
      "createdAt": "2025-01-15T10:00:00Z",
      "updatedAt": "2025-01-15T10:30:00Z",
      "updatedBy": "[email protected]",
      "data": {
        "rawPresignedUrl": "https://s3.amazonaws.com/bucket/reports/raw/report.json?signature=...",
        "mystiquePresignedUrl": "https://s3.amazonaws.com/bucket/reports/mystique/report.json?signature=..."
      }
    },
    {
      "id": "456e7890-e12b-34d5-a678-901234567890",
      "siteId": "123e4567-e89b-12d3-a456-426614174000",
      "reportType": "seo",
      "status": "processing",
      "reportPeriod": {
        "startDate": "2025-01-14",
        "endDate": "2025-01-14"
      },
      "comparisonPeriod": {
        "startDate": "2025-01-07",
        "endDate": "2025-01-13"
      },
      "storagePath": "/reports/123e4567-e89b-12d3-a456-426614174000/seo/456e7890-e12b-34d5-a678-901234567890/",
      "createdAt": "2025-01-14T15:30:00Z",
      "updatedAt": "2025-01-14T16:00:00Z",
      "updatedBy": "[email protected]"
    }
  ],
  "count": 2
}
Untitled diagram _ Mermaid Chart-2025-08-04-095236

GET /sites/{siteId}/reports/{reportId}

Request:

curl -X GET "https://api.spacecat.com/sites/123e4567-e89b-12d3-a456-426614174000/reports/987e6543-e21b-12d3-a456-426614174001" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response:

{
  "id": "987e6543-e21b-12d3-a456-426614174001",
  "siteId": "123e4567-e89b-12d3-a456-426614174000",
  "reportType": "performance",
  "status": "success",
  "reportPeriod": {
    "startDate": "2025-01-01",
    "endDate": "2025-01-31"
  },
  "comparisonPeriod": {
    "startDate": "2024-12-01",
    "endDate": "2024-12-31"
  },
  "storagePath": "/reports/123e4567-e89b-12d3-a456-426614174000/performance/987e6543-e21b-12d3-a456-426614174001/",
  "createdAt": "2025-01-15T10:00:00Z",
  "updatedAt": "2025-01-15T10:30:00Z",
  "updatedBy": "[email protected]",
  "data": {
    "rawPresignedUrl": "https://s3.amazonaws.com/bucket/reports/raw/report.json?signature=...",
    "mystiquePresignedUrl": "https://s3.amazonaws.com/bucket/reports/mystique/report.json?signature=..."
  }
}
Untitled diagram _ Mermaid Chart-2025-08-04-095320

DELETE /sites/{siteId}/reports/{reportId}

Request:

curl -X DELETE "https://api.spacecat.com/sites/123e4567-e89b-12d3-a456-426614174000/reports/987e6543-e21b-12d3-a456-426614174001" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response:

{
  "message": "Report deleted successfully",
  "siteId": "123e4567-e89b-12d3-a456-426614174000",
  "reportId": "987e6543-e21b-12d3-a456-426614174001"
}
Untitled diagram _ Mermaid Chart-2025-08-04-095346

Error Responses

Status Code Error Description
400 Bad Request Invalid UUID format, missing required fields, or report still processing
401 Unauthorized Missing or invalid authentication token/API key
403 Forbidden User does not have access to this site/organization
404 Not Found Site or report not found
500 Internal Server Error Queue unavailable, S3 error, or database error

Notes

  • All site and report IDs must be valid UUIDs
  • Report creation is asynchronous - jobs are queued for processing
  • Access control is enforced at the organization level
  • Reports are scoped to specific sites and cannot be accessed cross-tenant
  • Only reports with success status include presigned URLs for downloading
  • The GET /reports/{reportId} endpoint only returns successful reports (returns 400 for processing reports)
  • The DELETE endpoint removes both database records and associated S3 files (raw and Mystique analysis)
  • Supported report types: performance, accessibility, seo, security, conversion, cwv, optimization
  • Presigned URLs are valid for 7 days and provide access to both raw and AI-enhanced report data

Please ensure your pull request adheres to the following guidelines:

  • make sure to link the related issues in this description. Or if there's no issue created, make sure you
    describe here the problem you're solving.
  • when merging / squashing, make sure the fixed issue references are visible in the commits, for easy compilation of release notes

If the PR is changing the API specification:

  • make sure you add a "Not implemented yet" note the endpoint description, if the implementation is not ready
    yet. Ideally, return a 501 status code with a message explaining the feature is not implemented yet.
  • make sure you add at least one example of the request and response.

If the PR is changing the API implementation or an entity exposed through the API:

  • make sure you update the API specification and the examples to reflect the changes.

If the PR is introducing a new audit type:

  • make sure you update the API specification with the type, schema of the audit result and an example

Related Issues

This PR implements the Reports API endpoints for managing report generation jobs. The implementation includes:

  • Complete CRUD operations for reports with S3 integration
  • Authentication and authorization middleware integration
  • Organization-based access control
  • Asynchronous report job processing via SQS
  • S3 file cleanup on report deletion (both raw and Mystique files)
  • Presigned URL generation for secure report downloads
  • Comprehensive test coverage (12 deleteReport tests + full suite passing)
  • UML sequence diagrams for API flows

Thanks for contributing!

Copy link

This PR will trigger a minor release when merged.

}

// Validate report period
if (!isNonEmptyObject(data.reportPeriod)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can create an isValidPeriod(data.reportPeriod, 'reportPeriod') function to reduce duplication.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


// Check if a report with the same parameters already exists
const existingReports = await Report.allBySiteId(siteId);
const existingReport = existingReports.find((report) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filter out the failed reports.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}

// Deep comparison of report periods
const periodsMatch = JSON.stringify(reportPeriod) === JSON.stringify(data.reportPeriod);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a comparePeriod(period1, period2) function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

// Only generate presigned URLs for successful reports
if (report.getStatus() === 'success') {
try {
const rawReportKey = `${report.getStoragePath()}raw/report.json`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can create getter for this in model. report.getRawPath(), report.getMystiquePath().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This data is not saved in model, it is computed on API call and returned, there's no valid point to save presigned url in dynamo.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not talking about saving this. Add a getter function to get this path in the model.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, adding 👍

}

// Delete S3 files if they exist (only for successful reports)
if (report.getStatus() === 'success' && report.getStoragePath()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check if failed processing also creates s3 objects. I can't find implementation of the consumer side.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That will be in the spacecat-reporting-worker, once the POST SQS message goes to spacecat-reporting-worker.

Signed-off-by: Sahil Silare <[email protected]>
@ssilare-adobe ssilare-adobe requested a review from sandsinh August 5, 2025 09:59
// Check if a report with the same parameters already exists
const existingReports = await Report.allBySiteId(siteId);
// Filter out failed reports (only consider successful reports for duplicate checking)
const successfulReports = existingReports.filter((report) => report.getStatus() === 'success');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also consider processing statuses.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants