File size: 9,838 Bytes
6140a89
 
 
 
dec8c1d
ca925bb
6140a89
a056cb8
6140a89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fc82495
a056cb8
182a396
49502e6
 
 
 
 
a056cb8
182a396
e0f2ba1
49502e6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a056cb8
 
 
 
 
 
 
9b2b6de
a056cb8
 
9b2b6de
 
 
 
c350578
 
 
a056cb8
 
 
 
9b2b6de
284b445
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# bubble_api_calls.py
import os
import json
import requests
import pandas as pd
import logging


def fetch_linkedin_token_from_bubble(url_user_token_str: str):
    """
    Fetches LinkedIn access token from Bubble.io API using the state value (url_user_token_str).
    The token is expected in a 'Raw_text' field as a JSON string, which is then parsed.
    
    Args:
        url_user_token_str: The state value (token from URL) to query Bubble.

    Returns:
        tuple: (parsed_token_dict, status_message)
               parsed_token_dict is the dictionary containing the token (e.g., {"access_token": "value"}) 
               or None if an error occurred or token not found.
               status_message is a string describing the outcome of the API call.
    """
    bubble_api_key = os.environ.get("Bubble_API")
    if not bubble_api_key:
        error_msg = "❌ Bubble API Error: The 'Bubble_API' environment variable is not set."
        print(error_msg)
        return None, error_msg

    if not url_user_token_str or "not found" in url_user_token_str or "Could not access" in url_user_token_str:
        status_msg = f"ℹ️ No valid user token from URL to query Bubble. ({url_user_token_str})"
        print(status_msg)
        return None, status_msg

    base_url = "https://app.ingaze.ai/version-test/api/1.1/obj/Linkedin_access"
    constraints = [{"key": "state", "constraint_type": "equals", "value": url_user_token_str}]
    params = {'constraints': json.dumps(constraints)}
    headers = {"Authorization": f"Bearer {bubble_api_key}"}
    
    status_message = f"Attempting to fetch token from Bubble for state: {url_user_token_str}..."
    print(status_message)
    parsed_token_dict = None
    response = None

    try:
        response = requests.get(base_url, params=params, headers=headers, timeout=15)
        response.raise_for_status()
        
        data = response.json()
        results = data.get("response", {}).get("results", [])
        
        if results:
            raw_text_from_bubble = results[0].get("Raw_text", None)

            if raw_text_from_bubble and isinstance(raw_text_from_bubble, str):
                try:
                    temp_parsed_dict = json.loads(raw_text_from_bubble)
                    if isinstance(temp_parsed_dict, dict) and "access_token" in temp_parsed_dict:
                        parsed_token_dict = temp_parsed_dict # Successfully parsed and has access_token
                        status_message = f"✅ LinkedIn Token successfully fetched and parsed from Bubble 'Raw_text' for state: {url_user_token_str}"
                    elif isinstance(temp_parsed_dict, dict):
                        status_message = (f"⚠️ Bubble API: 'access_token' key missing in parsed 'Raw_text' dictionary for state: {url_user_token_str}. Parsed: {temp_parsed_dict}")
                    else: # Not a dict
                        status_message = (f"⚠️ Bubble API: 'Raw_text' field did not contain a valid JSON dictionary string. "
                                          f"Content type: {type(raw_text_from_bubble)}, Value: {raw_text_from_bubble}")
                except json.JSONDecodeError as e:
                    status_message = (f"⚠️ Bubble API: Error decoding 'Raw_text' JSON string: {e}. "
                                      f"Content: {raw_text_from_bubble}")
            elif raw_text_from_bubble: # It exists but is not a string
                 status_message = (f"⚠️ Bubble API: 'Raw_text' field was not a string. "
                                  f"Type: {type(raw_text_from_bubble)}, Value: {raw_text_from_bubble}")
            else: # Raw_text not found or null
                 status_message = (f"⚠️ Bubble API: Token field ('Raw_text') "
                                  f"not found or is null in response for state: {url_user_token_str}. Result: {results[0]}")
        else: # No results from Bubble for the given state
            status_message = f"❌ Bubble API: No results found for state: {url_user_token_str}"
            
    except requests.exceptions.HTTPError as http_err:
        error_details = response.text if response else "No response content"
        status_message = f"❌ Bubble API HTTP error: {http_err} - Response: {error_details}"
    except requests.exceptions.Timeout:
        status_message = "❌ Bubble API Request timed out."
    except requests.exceptions.RequestException as req_err:
        status_message = f"❌ Bubble API Request error: {req_err}"
    except json.JSONDecodeError as json_err: # Error decoding the main Bubble response
        error_details = response.text if response else "No response content"
        status_message = f"❌ Bubble API main response JSON decode error: {json_err}. Response: {error_details}"
    except Exception as e:
        status_message = f"❌ An unexpected error occurred while fetching from Bubble: {str(e)}"
    
    print(status_message) # Log the final status message
    return parsed_token_dict

def fetch_linkedin_posts_data_from_bubble(org_urn: str, data_type:str):
    bubble_api_key = os.environ.get("Bubble_API")
    if not bubble_api_key:
        error_msg = "❌ Bubble API Error: The 'Bubble_API' environment variable is not set."
        print(error_msg)
        return None, error_msg

    base_url = f"https://app.ingaze.ai/version-test/api/1.1/obj/{data_type}"
    constraints = [{"key": "organization_urn", "constraint_type": "equals", "value": org_urn}]
    params = {'constraints': json.dumps(constraints)}
    headers = {"Authorization": f"Bearer {bubble_api_key}"}

    status_message = f"Attempting to fetch posts from Bubble for urn: {org_urn}..."
    print(status_message)

    try:
        response = requests.get(base_url, params=params, headers=headers, timeout=15)
        response.raise_for_status()

        data = response.json()
        results = data.get("response", {}).get("results", [])

        if results:
            df = pd.DataFrame(results)
            print(f"Successfully retrieved {len(df)} posts.")
            return df, None
        else:
            print("No posts found for the given org_urn.")
            return pd.DataFrame(), None

    except requests.exceptions.RequestException as e:
        error_msg = f"❌ Bubble API Error: {str(e)}"
        print(error_msg)
        return None, error_msg


def bulk_upload_to_bubble(data, data_type):
    api_token = os.environ.get("Bubble_API")
    url = f"https://app.ingaze.ai/version-test/api/1.1/obj/{data_type}/bulk"
    headers = {
        "Authorization": f"Bearer {api_token}",
        "Content-Type": "text/plain"
    }

    # Convert list of dicts to newline-separated JSON strings
    payload = "\n".join(json.dumps(item) for item in data)
    response = requests.post(url, headers=headers, data=payload)

    print("Payload being sent:")
    print(payload)
    
    if response.status_code == 200:
        print(f"Successfully uploaded {len(data)} records to {data_type}.")
    else:
        print(f"Failed to upload data to {data_type}. Status Code: {response.status_code}, Response: {response.text}")

def update_record_in_bubble(table_name, record_id, payload_to_update):
    """
    Updates an existing record in a Bubble.io table using a PATCH request.

    Args:
        table_name (str): The name of the Bubble table (e.g., "User", "Product").
        record_id (str): The unique ID of the record to update.
        payload_to_update (dict): A dictionary where keys are field names (slugs)
                                  and values are the new values for those fields.
    Returns:
        bool: True if the update was successful, False otherwise.
    """
    bubble_api_key = os.environ.get("Bubble_API")
    if not record_id:
        logging.error(f"Record ID is missing. Cannot update record in Bubble table '{table_name}'.")
        return False
    if not payload_to_update:
        logging.warning(f"Payload to update is empty for record_id '{record_id}' in Bubble table '{table_name}'. Nothing to update.")
        # Consider this a success as there's no error, just no action.
        # Depending on desired behavior, you might return False or raise an error.
        return True

    # Construct the API endpoint for a specific record
    # Example: https://<app_name>.bubbleapps.io/api/1.1/obj/<table_name>/<record_id>
    api_endpoint = f"https://app.ingaze.ai/version-test/api/1.1/obj/{table_name}/{record_id}"
    headers = {
        "Authorization": f"Bearer {bubble_api_key}",
        "Content-Type": "application/json"
    }

    logging.debug(f"Attempting to update record '{record_id}' in table '{table_name}' at endpoint '{api_endpoint}' with payload: {payload_to_update}")

    try:
        response = requests.patch(api_endpoint, json=payload_to_update, headers=headers)
        response.raise_for_status()  # Raises an HTTPError for bad responses (4XX or 5XX)
        logging.info(f"Successfully updated record '{record_id}' in Bubble table '{table_name}'.")
        return True
    except requests.exceptions.HTTPError as http_err:
        # Log more details from the response if available
        error_details = ""
        try:
            error_details = response.json() # Bubble often returns JSON errors
        except ValueError: # If response is not JSON
            error_details = response.text
        logging.error(f"HTTP error occurred while updating record '{record_id}' in '{table_name}': {http_err}. Response: {error_details}")
    except requests.exceptions.RequestException as req_err:
        logging.error(f"Request exception occurred while updating record '{record_id}' in '{table_name}': {req_err}")
    except Exception as e:
        logging.error(f"An unexpected error occurred while updating record '{record_id}' in '{table_name}': {e}", exc_info=True)
    
    return False