---
# System prepended metadata

title: Question 3

---

Question 3
==

The Formstack student application form to collect applicants' information listed below
1. Personal Details
 	-  a. First Name
 	-  b. Last Name
 	-  c. Email
 	-  d. Gender
 	-  e. Where are you from?
 	-  f. How did you hear about Moringa School?
 	-  g. Current Employment Status
 	-  h. Class Start Date

2. Professional Background
	- a. What is your highest level of education completed?
	- b. What is the name of the institution you attended
	- c. Please select your area of study from the following
	- d. How would you describe your 	professional background?
	- e. Please select your industry from the following

3. Next of Kin Details
	- a. Name
	- b. Phone Number
	- c. Email
	- d. Data Processing Consent[checkbox]

- A. Write a query that submits the form with an applicant's bio-data via API

- B. Provide a sample JavaScript snippet to validate the start date (ensuring it’s at least two weeks from the current date).


## Solution

   
- [How to submit an applicant's bio-data via API](#How-to-submit-an-applicants-bio-data-via-API)

Referrence material: https://developers.formstack.com/docs/getting-started


- First let's update our code structure, since I'm using a single repository, this is the approach I choose to organise my files


```
    client
        handlers.js
        index.js
        index.html

    config
        __init__.py
    models
       courses.py
       forms.py
    
    routes
        __init__.py
        courses.py
        forms.py
    main.py
    .env
    .gitignore
    requirements.txt
    
``` 

update the .env file

```
FORM_API_URL=YOUR_FORM_API_URL
FORM_CLIENT_ID=YOUR_FORM_CLIENT_ID
FORM_CLIENT_SECRET=YOUR_FORM_CLIENT_SECRET
REDIERCT_URL=REDIERCT_URL
```

To follow along

git clone this repository: https://github.com/x0can/LMS-API

Navigate to formstack admin page and obtain the above environment variables

Next 

`pip3 install -r requirements.txt`

`python3 main.py`

On your browser send this request to obtain redirect_url for generating `auth token`
```
GET  http://127.0.0.1:5000/api/authorize
```



### How to submit an applicant's bio-data via API


Requirements and Assumptions:

1. Need to be an Admin to create an application on FormStack and obtain configurations
2. Follow through the process below on how to get Admin API environment variable 
`FORM_API_URL,
    FORM_CLIENT_ID,
    FORM_CLIENT_SECRET,
    REDIRECT_URL,
    `
    
    Formstack Aouth2: https://developers.formstack.com/reference/oauth2-authorize-get
    
- First we need to obtain an OAuth2 token for us to proceed. 

Reference material: https://developers.formstack.com/reference/oauth2-token-post

- Before genearating the token, let's create a `callback` url that will work as our `redirect_url`. It will handle the flow where you first get the authorization code and then exchange it for the OAuth2 token using the `get_oauth2_token` method below, returning all the relevant token data in the response.


Steps
1. Generate the authorization URL and prompt the user to visit it
2. The user is redirected 
3. Extract `code` from the URL and store it
4. Use the `code` to retrieve the OAuth2 token

github: https://github.com/x0can/LMS-API/blob/main/models/forms.py


```
GET  /api/v2/oauth2/authorize"
POST /api/v2/oauth2/token 
```



models.forms.py
```python=-
import requests


class FormProcess:
    def __init__(self, api_url, client_id, client_secret, redirect_uri, code=None,access_token=None):
        self.api_url = api_url
        self.client_id = client_id
        self.client_secret = client_secret
        self.redirect_uri = redirect_uri
        self.code = code
        self.access_token=access_token
        self.headers = {
                "Authorization": f"Bearer {self.access_token}",
                "Content-Type": "application/json"
            }


    def handle_redirect_callback_code(self, code):
        self.code = code
        return code

    def authorize_aouth2(self):

        endpoint = f"{self.api_url}/api/v2/oauth2/authorize"
        try:
            # Construct the authorization URL
            auth_url = f"{endpoint}?client_id={self.client_id}&redirect_uri={self.redirect_uri}&response_type=code"
            return auth_url

        except requests.exceptions.RequestException as e:
            raise Exception(f"Error generating OAuth2 token {str(e)}")

    def get_oauth2_token(self):
        """
        Generates an OAuth2 token using the Formstack API.
        """

        if not self.code:
            return "Authorization code is missing. Please authorize first."

        # Define endpoint and payload
        endpoint = f"{self.api_url}/oauth2/token"
        payload = {
            "grant_type": "authorization_code",
            "client_id": self.client_id,
            "client_secret": self.client_secret,
            "redirect_uri": self.redirect_uri,
            "code": self.code
        }

        try:
            # Send POST request to get the token
            response = requests.post(endpoint, data=payload)

            # Raise an error if the request failed
            response.raise_for_status()

            # Return the access token if successful
            token_data = response.json()
            access_token = token_data.get('access_token')
            self.access_token = access_token
            
            return self.access_token
        except requests.exceptions.RequestException as e:
            raise Exception(f"Error generating OAuth2 token {str(e)}")
            
    def handle_token(self, access_token):
        self.access_token = access_token
        return "Authorization Successfull"            
```

routes.forms.py

```python=
from flask import Blueprint, request, jsonify, session
from models.forms import FormProcess
from config import Config

form_routes = Blueprint('form_routes', __name__)


form_handler = FormProcess(
    Config.FORM_API_URL,
    Config.FORM_CLIENT_ID,
    Config.FORM_CLIENT_SECRET,
    Config.REDIRECT_URL # Always set this as '/api/callback'
)


@form_routes.route('/api/authorize')
def authorize():
    """
    Auto redirects to the authorization URL to start the OAuth2 flow.
    """
    auth_url = form_handler.authorize_aouth2()

    if auth_url:
        return redirect(auth_url)  # Redirect the user to the authorization URL
    return "Failed to generate authorization URL."



#Always set this as 'redirect_url'
@form_routes.route('/api/callback', methods=['GET', 'POST'])
def callback():
    """
    Handles the OAuth2 redirect callback and processes the authorization code.
    """

    # Check if the request is a GET or POST request
    if request.method == 'GET':
        # Handle query parameters (like 'code')
        auth_code = request.args.get('code')
        if auth_code:
            form_handler.handle_redirect_callback_code(auth_code)

            token_data = form_handler.get_oauth2_token()

            if token_data:
                # Return the full token data
                return f"Authorization successful. You can close this window.\n {jsonify(token_data)}"
            else:
                return "Failed to retrieve the access token.", 500
        else:
            return "Authorization failed."

    elif request.method == 'POST':
        # Handle JSON payload
        data = request.get_json()

        access_token = data.get('access_token') if data else None
        if access_token:
            status =  form_handler.handle_token(access_token)
            return jsonify(status), 200
            
        else:
            return "Authorization failed.", 500


```


- Next we create the `POST` request to submit applicants data

Referrence material: https://developers.formstack.com/reference/submissions

```
POST api/v2/form/{id}/submission.json
```

Assumptions: Data validation is handled on the client, you can skip to check [here](#How-to-Validate-the-start-date) how the start date is validated to make sure it's at least two weeks from the current date

models.forms.py
```python=
class FormProcess:

...
        def submit_formstack_application(self, form_id, applicant_data):
            """
            Submits an application to the Formstack API using OAuth2 tokens.
            """
            # Endpoint for form submissions
            endpoint = f"{self.api_url}/form/{form_id}/submission.json"

            try:

                #get access_token
                access_token = self.get_oauth2_token()

                # Define headers for authentication
                headers = {
                    "Authorization": f"Bearer {access_token}",
                    "Content-Type": "application/json"
                }

                # Send POST request to Formstack
                response = requests.post(endpoint, json=applicant_data, headers=headers)

                # Raise an error if the request failed
                response.raise_for_status()

                # Return the JSON response if successful
                return response.json()
            except requests.exceptions.RequestException as e:
                print(f"Error submitting the form: {e}")
                return None


```

When testing you might incur the following error,`Authorization code is missing. Please authorize first.` 

If you do, send a request to this endpoint

```
GET Http://Your-local-host/api/authorize
```

Then resume the process

create the '/submit-form' endpoint

routes.forms.py
```python=

...
@form_routes.route('/api/submit_form', methods=['POST'])
def submit_form():
    data = request.json

    # Validate the required fields in the form data
    required_fields = [
        "form_id"
        "name",
        "type",
        "first_name", "last_name", "email", "gender", "from_location", "source",
        "employment_status", "start_date", "education_level", "institution",
        "area_of_study", "professional_background", "industry", "kin_name",
        "kin_phone", "kin_email", "consent"
    ]

    missing_fields = [field for field in required_fields if field not in data]

    if missing_fields:
        return jsonify({"error": f"Missing required fields. {missing_fields}"}), 400

    try:
        # Call the submit_form_data method to send the data
        response_data, status_code = form_handler.submit_formstack_application(
            data['form_id'], data)

        return jsonify(response_data), status_code
    except Exception as e:
        return jsonify({"error": str(e)}), 500


```



### How to Validate the start date


Validate data before sending the `POST` request to the API endpoint `/api/submit-form` above using JavaScript

process

- Create an input validation function to check if the date is two weeks from current date
- Check if applicant's start date is okay and
- Send a `POST` request to our backend



Test by starting a server on the following path `/clients/index.html`

Or simply open the html file on a browser.



client.handlers.js
```javascript=
/**
 * Validates the start date to ensure it’s at least two weeks from the current date.
 * @param {string} startDate - The date to be validated in 'YYYY-MM-DD' format.
 * @returns {boolean} - Returns true if the start date is valid, false otherwise.
 */

// validator

const validateStartDate = (startDate) => {
  const today = new Date();
  const startDateObj = new Date(startDate);
  const twoWeeksFromToday = new Date(today);
  twoWeeksFromToday.setDate(today.getDate() + 14); // Set date to 14 days from today

  // Early return if the start date is invalid
  return (
    startDateObj >= twoWeeksFromToday || {
      error: "Start date must be at least 14 days from today.",
    }
  );
};

/**
 * Submits the form to the following endpoint {url}/submit_form.
 * @param {object} formData - The form data to send over to the API.
 * @param {string} url - The API endpoint url, must match the server's endpoint.
 * @returns {object} - Returns object if successful, error otherwise.
 */

// handlers

const submitForm = async (formData, url) => {
  // Validate form data
  validDate = validateStartDate(formData["start_date"]);

  // If validation fails (validDate is an object with an error), return the error
  if (validDate.error) {
    return validDate; 
  }
  try {
    const response = await fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(formData),
    });

    const result = await response.json();

    if (!response.ok) {
      throw new Error(result.error || "Failed to submit form");
    }

    return result;
  } catch (error) {
    console.error("Error submitting form:", error.message);
    throw error;
  }
};



function handle_form() {
  const form = document.getElementById("submitForm");

  const url = "http://127.0.0.1:5000/api/submit_form";

  const formStatus = document.getElementById("formStatus");

  form.addEventListener("submit", async (e) => {
    e.preventDefault();

    const formData = {
      form_id: 'F1O1',
      name: form.name.value,
      type: form.type.value,
      first_name: form.first_name.value,
      last_name: form.last_name.value,
      email: form.email.value,
      gender: form.gender.value,
      from_location: form.from_location.value,
      source: form.source.value,
      employment_status: form.employment_status.value,
      start_date: form.start_date.value,
      education_level: form.education_level.value,
      institution: form.institution.value,
      area_of_study: form.area_of_study.value,
      professional_background: form.professional_background.value,
      industry: form.industry.value,
      kin_name: form.kin_name.value,
      kin_phone: form.kin_phone.value,
      kin_email: form.kin_email.value,
      consent: form.consent.checked,
    };

    try {
      const result = await submitForm(url, formData);

      const { error } = result;

      if (error) {
        formStatus.textContent = error;
        formStatus.style.color = "#F44336";
      } else {
        formStatus.textContent = "Form submitted successfully!";
        formStatus.style.color = "green";
      }
    } catch (error) {
      console.log(error.message);
      formStatus.textContent = error.message;
      formStatus.style.color = "#F44336";
    }
  });
}

handle_form();


```
To test the above

Run a live server inside the `/clients/index.html` file then submit the form there.
