Skip to content

Conversation

@GYFX35
Copy link
Owner

@GYFX35 GYFX35 commented Sep 18, 2025

This commit integrates the Global Biodiversity Information Facility (GBIF) API to fetch and display species occurrence data.

  • A new backend endpoint /api/gbif_occurrences is added to the Flask application to fetch the 5 most recent species occurrences from Togo.
  • The frontend is updated with a new section to display the data, including the species name and a link to the occurrence on the GBIF website.
  • The README.md is updated to document the new API integration.

Summary by Sourcery

Integrate GBIF API to fetch and display recent species occurrence data for Togo by adding a new backend endpoint, updating the frontend to show the data, and updating documentation.

New Features:

  • Add /api/gbif_occurrences Flask endpoint to retrieve the 5 latest species occurrences from GBIF for Togo
  • Fetch and render GBIF occurrences on the frontend with species names and links to GBIF

Documentation:

  • Document GBIF API integration and usage in the README

This commit integrates the Global Biodiversity Information Facility (GBIF) API to fetch and display species occurrence data.

- A new backend endpoint `/api/gbif_occurrences` is added to the Flask application to fetch the 5 most recent species occurrences from Togo.
- The frontend is updated with a new section to display the data, including the species name and a link to the occurrence on the GBIF website.
- The README.md is updated to document the new API integration.
@sourcery-ai
Copy link

sourcery-ai bot commented Sep 18, 2025

Reviewer's Guide

This PR integrates the GBIF API by adding a new backend endpoint to fetch recent species occurrences in Togo, updating the frontend to render this data in a dedicated section, and extending project documentation to describe the new integration.

Sequence diagram for fetching and displaying GBIF occurrences

sequenceDiagram
    participant User
    participant Frontend
    participant Backend
    participant GBIF_API
    User->>Frontend: Load page
    Frontend->>Backend: GET /api/gbif_occurrences
    Backend->>GBIF_API: GET occurrence/search?country=TG&limit=5
    GBIF_API-->>Backend: JSON occurrence data
    Backend-->>Frontend: JSON formatted species data
    Frontend-->>User: Render species list with links
Loading

Class diagram for GBIF occurrence data structure

classDiagram
    class GBIFOccurrence {
        +species: string
        +url: string
    }
    class GBIFAPIResponse {
        +results: list~GBIFOccurrence~
    }
    GBIFAPIResponse "1" -- "*" GBIFOccurrence: contains
Loading

File-Level Changes

Change Details Files
Frontend integration of GBIF occurrence data
  • Add new biodiversity section and gbif-occurrences container in index.html
  • Select gbifOccurrencesContainer and invoke fetchGbifData on page load in script.js
  • Implement fetchGbifData to call /api/gbif_occurrences, render results, and handle errors
eco_project/frontend/index.html
eco_project/frontend/script.js
New backend endpoint for GBIF data
  • Add /api/gbif_occurrences route to Flask app
  • Fetch and parse GBIF API results for Togo with limit parameter
  • Format response with species names and occurrence URLs, with error handling
eco_project/backend/app.py
Update documentation for GBIF integration
  • Add GBIF API Integration section to README with usage details
README.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@guardrails
Copy link

guardrails bot commented Sep 18, 2025

⚠️ We detected 2 security issues in this pull request:

Insecure Processing of Data (2)
Severity Details Docs
High Title: Server-Side Request Forgery (Requests)
response = requests.get(url)
📚
Medium Title: Unescaped user input in HTML
gbifOccurrencesContainer.innerHTML = `<p>Error fetching data: ${data.error}</p>`;
📚

More info on how to fix Insecure Processing of Data in Python and JavaScript.


👉 Go to the dashboard for detailed results.

📥 Happy? Share your feedback with us.

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes and found some issues that need to be addressed.

Blocking issues:

  • Detected a 'requests' call without a timeout set. By default, 'requests' calls wait until the connection is closed. This means a 'requests' call without a timeout will hang the program if a response is never received. Consider setting a timeout for all 'requests'. (link)
  • User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities (link)
  • User controlled data in a gbifOccurrencesContainer.innerHTML is an anti-pattern that can lead to XSS vulnerabilities (link)
  • User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities (link)
  • User controlled data in a gbifOccurrencesContainer.innerHTML is an anti-pattern that can lead to XSS vulnerabilities (link)
  • User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities (link)
  • User controlled data in a gbifOccurrencesContainer.innerHTML is an anti-pattern that can lead to XSS vulnerabilities (link)
Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `eco_project/backend/app.py:48-58` </location>
<code_context>
+        response = requests.get(url)
+        data = response.json()
+
+        if data and data['results']:
+            # Clean and format the data
+            formatted_data = []
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Check for 'results' key existence before accessing.

Using 'data.get('results')' instead of 'data['results']' will prevent potential KeyErrors if 'results' is missing from the response.

```suggestion
        if data and data.get('results'):
            # Clean and format the data
            formatted_data = []
            for entry in data.get('results', []):
                formatted_data.append({
                    'species': entry.get('scientificName', 'N/A'),
                    'url': f"https://www.gbif.org/occurrence/{entry['key']}"
                })
            return jsonify(formatted_data)
        else:
            return jsonify({"error": "No data found for the selected criteria."}), 404
```
</issue_to_address>

### Comment 2
<location> `eco_project/backend/app.py:52-54` </location>
<code_context>
+            # Clean and format the data
+            formatted_data = []
+            for entry in data['results']:
+                formatted_data.append({
+                    'species': entry.get('scientificName', 'N/A'),
+                    'url': f"https://www.gbif.org/occurrence/{entry['key']}"
+                })
+            return jsonify(formatted_data)
</code_context>

<issue_to_address>
**issue:** Handle missing 'key' in GBIF results gracefully.

Using 'entry.get('key')' with a default value or skipping entries without 'key' will prevent KeyError exceptions.
</issue_to_address>

### Comment 3
<location> `eco_project/frontend/script.js:74` </location>
<code_context>
+
+            let html = '<ul>';
+            data.forEach(item => {
+                html += `<li><a href="${item.url}" target="_blank">${item.species}</a></li>`;
+            });
+            html += '</ul>';
</code_context>

<issue_to_address>
**🚨 issue (security):** Escape HTML to prevent XSS vulnerabilities.

Escaping 'item.species' and 'item.url' before rendering will help prevent XSS if backend data is not sanitized.
</issue_to_address>

### Comment 4
<location> `eco_project/backend/app.py:45` </location>
<code_context>
        response = requests.get(url)
</code_context>

<issue_to_address>
**security (python.requests.best-practice.use-timeout):** Detected a 'requests' call without a timeout set. By default, 'requests' calls wait until the connection is closed. This means a 'requests' call without a timeout will hang the program if a response is never received. Consider setting a timeout for all 'requests'.

```suggestion
        response = requests.get(url, timeout=30)
```

*Source: opengrep*
</issue_to_address>

### Comment 5
<location> `eco_project/frontend/script.js:68` </location>
<code_context>
                gbifOccurrencesContainer.innerHTML = `<p>Error fetching data: ${data.error}</p>`;
</code_context>

<issue_to_address>
**security (javascript.browser.security.insecure-document-method):** User controlled data in methods like `innerHTML`, `outerHTML` or `document.write` is an anti-pattern that can lead to XSS vulnerabilities

*Source: opengrep*
</issue_to_address>

### Comment 6
<location> `eco_project/frontend/script.js:68` </location>
<code_context>
                gbifOccurrencesContainer.innerHTML = `<p>Error fetching data: ${data.error}</p>`;
</code_context>

<issue_to_address>
**security (javascript.browser.security.insecure-innerhtml):** User controlled data in a `gbifOccurrencesContainer.innerHTML` is an anti-pattern that can lead to XSS vulnerabilities

*Source: opengrep*
</issue_to_address>

### Comment 7
<location> `eco_project/frontend/script.js:78` </location>
<code_context>
            gbifOccurrencesContainer.innerHTML = html;
</code_context>

<issue_to_address>
**security (javascript.browser.security.insecure-document-method):** User controlled data in methods like `innerHTML`, `outerHTML` or `document.write` is an anti-pattern that can lead to XSS vulnerabilities

*Source: opengrep*
</issue_to_address>

### Comment 8
<location> `eco_project/frontend/script.js:78` </location>
<code_context>
            gbifOccurrencesContainer.innerHTML = html;
</code_context>

<issue_to_address>
**security (javascript.browser.security.insecure-innerhtml):** User controlled data in a `gbifOccurrencesContainer.innerHTML` is an anti-pattern that can lead to XSS vulnerabilities

*Source: opengrep*
</issue_to_address>

### Comment 9
<location> `eco_project/frontend/script.js:81` </location>
<code_context>
            gbifOccurrencesContainer.innerHTML = `<p>Error fetching data: ${error.message}</p>`;
</code_context>

<issue_to_address>
**security (javascript.browser.security.insecure-document-method):** User controlled data in methods like `innerHTML`, `outerHTML` or `document.write` is an anti-pattern that can lead to XSS vulnerabilities

*Source: opengrep*
</issue_to_address>

### Comment 10
<location> `eco_project/frontend/script.js:81` </location>
<code_context>
            gbifOccurrencesContainer.innerHTML = `<p>Error fetching data: ${error.message}</p>`;
</code_context>

<issue_to_address>
**security (javascript.browser.security.insecure-innerhtml):** User controlled data in a `gbifOccurrencesContainer.innerHTML` is an anti-pattern that can lead to XSS vulnerabilities

*Source: opengrep*
</issue_to_address>

### Comment 11
<location> `eco_project/frontend/script.js:59-83` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Avoid function declarations, favouring function assignment expressions, inside blocks. ([`avoid-function-declarations-in-blocks`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/JavaScript/Default-Rules/avoid-function-declarations-in-blocks))

<details><summary>Explanation</summary>Function declarations may be hoisted in Javascript, but the behaviour is inconsistent between browsers.
Hoisting is generally confusing and should be avoided. Rather than using function declarations inside blocks, you
should use function expressions, which create functions in-scope.
</details>
</issue_to_address>

### Comment 12
<location> `eco_project/backend/app.py:50` </location>
<code_context>
@app.route('/api/gbif_occurrences')
def gbif_occurrences():
    # GBIF API URL for occurrences in Togo
    url = "https://api.gbif.org/v1/occurrence/search?country=TG&limit=5"

    try:
        response = requests.get(url)
        data = response.json()

        if data and data['results']:
            # Clean and format the data
            formatted_data = []
            for entry in data['results']:
                formatted_data.append({
                    'species': entry.get('scientificName', 'N/A'),
                    'url': f"https://www.gbif.org/occurrence/{entry['key']}"
                })
            return jsonify(formatted_data)
        else:
            return jsonify({"error": "No data found for the selected criteria."}), 404

    except requests.exceptions.RequestException as e:
        return jsonify({"error": str(e)}), 500

</code_context>

<issue_to_address>
**issue (code-quality):** Convert for loop into list comprehension ([`list-comprehension`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/list-comprehension/))
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 59 to 83
async function fetchGbifData() {
try {
const response = await fetch('/api/gbif_occurrences');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();

if (data.error) {
gbifOccurrencesContainer.innerHTML = `<p>Error fetching data: ${data.error}</p>`;
return;
}

let html = '<ul>';
data.forEach(item => {
html += `<li><a href="${item.url}" target="_blank">${item.species}</a></li>`;
});
html += '</ul>';

gbifOccurrencesContainer.innerHTML = html;

} catch (error) {
gbifOccurrencesContainer.innerHTML = `<p>Error fetching data: ${error.message}</p>`;
}
}
Copy link

Choose a reason for hiding this comment

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

issue (code-quality): Avoid function declarations, favouring function assignment expressions, inside blocks. (avoid-function-declarations-in-blocks)

ExplanationFunction declarations may be hoisted in Javascript, but the behaviour is inconsistent between browsers. Hoisting is generally confusing and should be avoided. Rather than using function declarations inside blocks, you should use function expressions, which create functions in-scope.


if data and data['results']:
# Clean and format the data
formatted_data = []
Copy link

Choose a reason for hiding this comment

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

issue (code-quality): Convert for loop into list comprehension (list-comprehension)

This commit adds a feature to search for species occurrences by country using the GBIF API.

- The backend endpoint `/api/gbif_occurrences` is updated to accept a `country` parameter.
- The frontend is updated with a search box to allow users to enter a country code and view the results.
- The README.md is updated to document the new search functionality.
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

New security issues found

Comment on lines +42 to +64
country_code = request.args.get('country', 'TG')

# GBIF API URL for occurrences
url = f"https://api.gbif.org/v1/occurrence/search?country={country_code}&limit=5"

try:
response = requests.get(url)
data = response.json()

if data and data['results']:
# Clean and format the data
formatted_data = []
for entry in data['results']:
formatted_data.append({
'species': entry.get('scientificName', 'N/A'),
'url': f"https://www.gbif.org/occurrence/{entry['key']}"
})
return jsonify(formatted_data)
else:
return jsonify({"error": "No data found for the selected criteria."}), 404

except requests.exceptions.RequestException as e:
return jsonify({"error": str(e)}), 500
Copy link

Choose a reason for hiding this comment

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

security (python.django.security.injection.ssrf.ssrf-injection-requests): Data from request object is passed to a new server-side request. This could lead to a server-side request forgery (SSRF). To mitigate, ensure that schemes and hosts are validated against an allowlist, do not forward the response to the user, and ensure proper authentication and transport-layer security in the proxied request. See https://owasp.org/www-community/attacks/Server_Side_Request_Forgery to learn more about SSRF vulnerabilities.

Source: opengrep

url = f"https://api.gbif.org/v1/occurrence/search?country={country_code}&limit=5"

try:
response = requests.get(url)
Copy link

Choose a reason for hiding this comment

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

security (python.requests.best-practice.use-timeout): Detected a 'requests' call without a timeout set. By default, 'requests' calls wait until the connection is closed. This means a 'requests' call without a timeout will hang the program if a response is never received. Consider setting a timeout for all 'requests'.

Suggested change
response = requests.get(url)
response = requests.get(url, timeout=30)

Source: opengrep

const data = await response.json();

if (data.error) {
gbifOccurrencesContainer.innerHTML = `<p>Error fetching data: ${data.error}</p>`;
Copy link

Choose a reason for hiding this comment

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

security (javascript.browser.security.insecure-document-method): User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities

Source: opengrep

const data = await response.json();

if (data.error) {
gbifOccurrencesContainer.innerHTML = `<p>Error fetching data: ${data.error}</p>`;
Copy link

Choose a reason for hiding this comment

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

security (javascript.browser.security.insecure-innerhtml): User controlled data in a gbifOccurrencesContainer.innerHTML is an anti-pattern that can lead to XSS vulnerabilities

Source: opengrep

});
html += '</ul>';

gbifOccurrencesContainer.innerHTML = html;
Copy link

Choose a reason for hiding this comment

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

security (javascript.browser.security.insecure-document-method): User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities

Source: opengrep

});
html += '</ul>';

gbifOccurrencesContainer.innerHTML = html;
Copy link

Choose a reason for hiding this comment

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

security (javascript.browser.security.insecure-innerhtml): User controlled data in a gbifOccurrencesContainer.innerHTML is an anti-pattern that can lead to XSS vulnerabilities

Source: opengrep

gbifOccurrencesContainer.innerHTML = html;

} catch (error) {
gbifOccurrencesContainer.innerHTML = `<p>Error fetching data: ${error.message}</p>`;
Copy link

Choose a reason for hiding this comment

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

security (javascript.browser.security.insecure-document-method): User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities

Source: opengrep

gbifOccurrencesContainer.innerHTML = html;

} catch (error) {
gbifOccurrencesContainer.innerHTML = `<p>Error fetching data: ${error.message}</p>`;
Copy link

Choose a reason for hiding this comment

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

security (javascript.browser.security.insecure-innerhtml): User controlled data in a gbifOccurrencesContainer.innerHTML is an anti-pattern that can lead to XSS vulnerabilities

Source: opengrep

This commit adds a feature to search for species occurrences by country using the GBIF API.

- The backend endpoint `/api/gbif_occurrences` is updated to accept a `country` parameter.
- The frontend is updated with a search box to allow users to enter a country code and view the results.
- The README.md is updated to document the new search functionality.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants