Skip to content

Validate configuration fields in fetchData#34

Merged
alikon merged 10 commits intomainfrom
export-add-validation
Mar 14, 2026
Merged

Validate configuration fields in fetchData#34
alikon merged 10 commits intomainfrom
export-add-validation

Conversation

@alikon
Copy link
Copy Markdown
Owner

@alikon alikon commented Mar 8, 2026

Added validation for required configuration fields in fetchData function.

Summary by Sourcery

Add configuration validation and improved user messaging for article export operations.

New Features:

  • Validate configuration options before running fetchData and abort when required fields are missing.
  • Introduce article data validation before creating or updating articles to ensure required fields are present.

Bug Fixes:

  • Prevent export actions from proceeding when configuration or article data is incomplete, avoiding invalid API calls.

Enhancements:

  • Standardize success, info, and error messaging with typed alerts and auto-dismiss behavior.
  • Improve error handling for network and CORS failures across category, post, patch, and article checks with clearer, method-specific messages.

Added validation for required configuration fields in fetchData function.
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Mar 8, 2026

Reviewer's Guide

Adds upfront configuration validation to fetchData and refactors messaging and error handling across export flows to provide clearer, typed alerts and prevent operations when required data is missing.

Sequence diagram for fetchData configuration validation and export flow

sequenceDiagram
  actor User
  participant Browser
  participant fetchData
  participant hasValidConfig
  participant checkCategory
  participant checkArticle
  participant postArticle
  participant patchArticle
  participant ApiServer

  User->>Browser: Click export button
  Browser->>fetchData: fetchData()
  fetchData->>Browser: window.Joomla.getOptions a_export
  fetchData->>hasValidConfig: hasValidConfig(options)
  hasValidConfig-->>fetchData: { ok, message, apiKey, auth, getUrl, postUrl }
  alt invalid configuration
    fetchData->>Browser: showMessage(message, error)
    fetchData-->>User: Display configuration error alert
  else valid configuration
    fetchData->>Browser: Insert loader spinner
    fetchData->>checkCategory: checkCategory(options)
    checkCategory->>ApiServer: GET category
    alt category check ok
      ApiServer-->>checkCategory: 2xx response with data
      checkCategory->>Browser: showMessage(Category check, success)
      checkCategory-->>fetchData: true
      fetchData->>checkArticle: checkArticle(options)
      checkArticle->>ApiServer: GET article
      alt article not found
        ApiServer-->>checkArticle: 2xx response, empty data
        checkArticle->>postArticle: postArticle(options)
        postArticle->>Browser: validate article fields
        alt missing title or catid
          postArticle->>Browser: showMessage(Cannot create article..., error)
          postArticle-->>checkArticle: false
        else valid article fields
          postArticle->>ApiServer: POST article
          ApiServer-->>postArticle: response
          alt POST ok
            postArticle->>Browser: showMessage(Article created, success)
            postArticle-->>checkArticle: true
          else POST error
            postArticle->>Browser: showMessage(Article not created..., error)
            postArticle-->>checkArticle: false
          end
        end
      else article found
        ApiServer-->>checkArticle: 2xx response with data
        checkArticle->>patchArticle: patchArticle(options, articleId)
        patchArticle->>Browser: validate article title
        alt missing title
          patchArticle->>Browser: showMessage(Cannot update article..., error)
          patchArticle-->>checkArticle: false
        else valid title
          patchArticle->>ApiServer: PATCH article
          ApiServer-->>patchArticle: response
          alt PATCH ok
            patchArticle->>Browser: showMessage(Article exported, success)
            patchArticle-->>checkArticle: true
          else PATCH error
            patchArticle->>Browser: showMessage(status text, error)
            patchArticle-->>checkArticle: false
          end
        end
      end
      checkArticle-->>fetchData: done
    else category check error
      ApiServer-->>checkCategory: error or non 2xx
      checkCategory->>Browser: showMessage(Category check: status..., error)
      checkCategory-->>fetchData: false
    end
    fetchData->>Browser: hide loader spinner
  end
Loading

File-Level Changes

Change Details Files
Add upfront configuration validation for fetchData and normalize required config fields.
  • Introduce hasValidConfig helper to validate and normalize options (apiKey, auth, GET and POST URLs).
  • Call hasValidConfig at the start of fetchData and abort execution with an error message when validation fails.
  • Log fetched options to the console for easier debugging.
src/plugins/content/export/media/js/aexport.js
Harden article operations with precondition checks and improved success/error messaging.
  • Add validation in postArticle to ensure article has been saved with title and category before creating.
  • Add validation in patchArticle to ensure article has been saved with a title before updating.
  • Update showMessage calls to include explicit status types (success/error) and normalize message text and return paths.
src/plugins/content/export/media/js/aexport.js
Improve network error handling and CORS diagnostics for GET/POST/PATCH flows.
  • Wrap network calls in checkCategory, postArticle, patchArticle, and checkArticle with richer error handling that distinguishes generic network failures from CORS-related TypeError cases.
  • Generate user-friendly error messages for method-specific CORS misconfigurations and display them as error alerts instead of raw error objects.
src/plugins/content/export/media/js/aexport.js
Refactor showMessage utility to support alert types, safe DOM access, and improved UX.
  • Extend showMessage to accept a type argument (info/success/error) and map to Bootstrap alert classes.
  • Ensure loader spinner is safely hidden if present and only one message element exists at a time by removing previous messages.
  • Change insertion point to after the toolbar container, style alerts with margin/border, and increase auto-dismiss timeout from 3s to 5s.
src/plugins/content/export/media/js/aexport.js

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

Copy link
Copy Markdown

@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 - I've left some high level feedback:

  • The validation block in fetchData is getting dense and hard to read; consider extracting it into a small helper (e.g., hasValidConfig(options)) or at least breaking the conditions into named boolean variables for each field to make intent clearer.
  • Using trim() on options.apiKey, options.auth, options.get, and options.post assumes they are always strings; it may be safer to coerce to string or guard with typeof === 'string' to avoid runtime errors when options are missing or of unexpected types.
  • The specific check options.apiKey === 'Bearer ' is quite narrow; if the goal is to prevent bare Bearer tokens without a value, you might want to validate with something like options.apiKey.startsWith('Bearer ') && options.apiKey.trim() === 'Bearer' or similar, and potentially surface a more specific error message for this case.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The validation block in `fetchData` is getting dense and hard to read; consider extracting it into a small helper (e.g., `hasValidConfig(options)`) or at least breaking the conditions into named boolean variables for each field to make intent clearer.
- Using `trim()` on `options.apiKey`, `options.auth`, `options.get`, and `options.post` assumes they are always strings; it may be safer to coerce to string or guard with `typeof === 'string'` to avoid runtime errors when options are missing or of unexpected types.
- The specific check `options.apiKey === 'Bearer '` is quite narrow; if the goal is to prevent bare Bearer tokens without a value, you might want to validate with something like `options.apiKey.startsWith('Bearer ') && options.apiKey.trim() === 'Bearer'` or similar, and potentially surface a more specific error message for this case.

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.

@alikon
Copy link
Copy Markdown
Owner Author

alikon commented Mar 8, 2026

@sourcery-ai review

Copy link
Copy Markdown

@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 - I've found 3 issues, and left some high level feedback:

  • In fetchData, hasValidConfig returns normalized apiKey/auth/getUrl/postUrl but you never use them to update options, so the subsequent calls still rely on the original (possibly untrimmed/invalid) values; consider either mutating options or passing the normalized values through.
  • The new CORS handling branches use error.message.includes('CORS') without first checking that error.message is a non-empty string, which can throw for unexpected error shapes; guard this with a type check before calling .includes.
  • The added console.log('options', options); in fetchData looks like debugging output and may be noisy in production—consider removing it or wrapping it behind a debug flag.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `fetchData`, `hasValidConfig` returns normalized `apiKey/auth/getUrl/postUrl` but you never use them to update `options`, so the subsequent calls still rely on the original (possibly untrimmed/invalid) values; consider either mutating `options` or passing the normalized values through.
- The new CORS handling branches use `error.message.includes('CORS')` without first checking that `error.message` is a non-empty string, which can throw for unexpected error shapes; guard this with a type check before calling `.includes`.
- The added `console.log('options', options);` in `fetchData` looks like debugging output and may be noisy in production—consider removing it or wrapping it behind a debug flag.

## Individual Comments

### Comment 1
<location path="src/plugins/content/export/media/js/aexport.js" line_range="15" />
<code_context>
     async function fetchData() {
       const options = window.Joomla.getOptions('a-export');
-      //console.log('options',options);
+      console.log('options',options);
+      const validation = hasValidConfig(options);
+
</code_context>
<issue_to_address>
**🚨 suggestion (security):** Remove or gate the `console.log` to avoid leaking configuration in production.

Logging the full `options` object can expose secrets (e.g., API keys) and adds noise in production. Please remove this log, guard it behind a debug flag, or log only non-sensitive fields.
</issue_to_address>

### Comment 2
<location path="src/plugins/content/export/media/js/aexport.js" line_range="16-18" />
<code_context>
       const options = window.Joomla.getOptions('a-export');
-      //console.log('options',options);
+      console.log('options',options);
+      const validation = hasValidConfig(options);
+
+      if (!validation.ok) {
+            showMessage(validation.message, 'error');
+            return;
</code_context>
<issue_to_address>
**suggestion:** Either use the normalized values returned from `hasValidConfig` or simplify its return type.

`hasValidConfig` normalizes `apiKey`, `auth`, `getUrl`, and `postUrl`, but `fetchData` only reads `ok`/`message` and keeps using the original `options`. This can be misleading if future changes assume `fetchData` uses the sanitized values. Either have `fetchData` consume the normalized values (e.g., merge them back into `options` or pass them through), or change `hasValidConfig` to only return `{ ok, message }` if normalization isn’t needed.

Suggested implementation:

```javascript
    async function fetchData() {
      const rawOptions = window.Joomla.getOptions('a-export');
      console.log('options', rawOptions);

      // hasValidConfig is expected to normalize config fields and return:
      // { ok, message, apiKey, auth, getUrl, postUrl, ... }
      const { ok, message, ...normalized } = hasValidConfig(rawOptions);

      if (!ok) {
        showMessage(message, 'error');
        return;
      }

      // Prefer normalized values if provided, fall back to raw options
      const options = {
        ...rawOptions,
        ...normalized,
      };

      document.getElementById('toolbar-upload')
        .insertAdjacentHTML(
          'afterbegin',
          '<span id="loader" class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true"></span>'
        );

      if (await checkCategory(options)) {
        await checkArticle(options);
      }

      const loader = document.getElementById('loader');
      loader.style.display = 'none';

```

To fully support this change, ensure `hasValidConfig` is implemented to:
1. Accept the raw `options` object.
2. Return an object of the form:
   `{ ok: boolean, message: string, apiKey, auth, getUrl, postUrl, ...otherNormalizedFields }`
   where the additional properties are the normalized versions of the corresponding fields from `options`.
If `hasValidConfig` currently returns a nested structure (e.g. `{ ok, message, options: normalizedOptions }`), adapt the destructuring in `fetchData` accordingly, e.g.:
`const { ok, message, options: normalized } = hasValidConfig(rawOptions);`
and then spread `normalized` into the final `options` object.
</issue_to_address>

### Comment 3
<location path="src/plugins/content/export/media/js/aexport.js" line_range="63" />
<code_context>
       } catch (error) {
-        showMessage(error);
+        let errorMsg = 'Network error occurred';
+        if (error.name === 'TypeError' || error.message.includes('CORS')) {
+          errorMsg = 'CORS error: The GET method is not allowed. Add "GET" to Access-Control-Allow-Methods header on the API server.';
+        } else if (error.message) {
</code_context>
<issue_to_address>
**issue:** Guard against `error.message` being undefined before calling `.includes`.

If `error.message` is undefined or not a string, `error.message.includes('CORS')` will throw and hide the original error. Consider guarding the check, e.g.:

```js
if (
  error.name === 'TypeError' ||
  (typeof error.message === 'string' && error.message.includes('CORS'))
) {
  // ...
}
```
The same adjustment should be applied in the POST and PATCH handlers for consistency and safety.
</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 +16 to +18
const validation = hasValidConfig(options);

if (!validation.ok) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion: Either use the normalized values returned from hasValidConfig or simplify its return type.

hasValidConfig normalizes apiKey, auth, getUrl, and postUrl, but fetchData only reads ok/message and keeps using the original options. This can be misleading if future changes assume fetchData uses the sanitized values. Either have fetchData consume the normalized values (e.g., merge them back into options or pass them through), or change hasValidConfig to only return { ok, message } if normalization isn’t needed.

Suggested implementation:

    async function fetchData() {
      const rawOptions = window.Joomla.getOptions('a-export');
      console.log('options', rawOptions);

      // hasValidConfig is expected to normalize config fields and return:
      // { ok, message, apiKey, auth, getUrl, postUrl, ... }
      const { ok, message, ...normalized } = hasValidConfig(rawOptions);

      if (!ok) {
        showMessage(message, 'error');
        return;
      }

      // Prefer normalized values if provided, fall back to raw options
      const options = {
        ...rawOptions,
        ...normalized,
      };

      document.getElementById('toolbar-upload')
        .insertAdjacentHTML(
          'afterbegin',
          '<span id="loader" class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true"></span>'
        );

      if (await checkCategory(options)) {
        await checkArticle(options);
      }

      const loader = document.getElementById('loader');
      loader.style.display = 'none';

To fully support this change, ensure hasValidConfig is implemented to:

  1. Accept the raw options object.
  2. Return an object of the form:
    { ok: boolean, message: string, apiKey, auth, getUrl, postUrl, ...otherNormalizedFields }
    where the additional properties are the normalized versions of the corresponding fields from options.
    If hasValidConfig currently returns a nested structure (e.g. { ok, message, options: normalizedOptions }), adapt the destructuring in fetchData accordingly, e.g.:
    const { ok, message, options: normalized } = hasValidConfig(rawOptions);
    and then spread normalized into the final options object.

} catch (error) {
showMessage(error);
let errorMsg = 'Network error occurred';
if (error.name === 'TypeError' || error.message.includes('CORS')) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue: Guard against error.message being undefined before calling .includes.

If error.message is undefined or not a string, error.message.includes('CORS') will throw and hide the original error. Consider guarding the check, e.g.:

if (
  error.name === 'TypeError' ||
  (typeof error.message === 'string' && error.message.includes('CORS'))
) {
  // ...
}

The same adjustment should be applied in the POST and PATCH handlers for consistency and safety.

@alikon alikon merged commit f2662bc into main Mar 14, 2026
26 checks passed
@alikon alikon deleted the export-add-validation branch March 14, 2026 09:15
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.

1 participant