World of Text
The World of Text: Automating Home Projects with Node-RED, MongoDB, Todoist, and More
As a homelab enthusiast, I constantly explore ways to streamline and automate daily tasks. My latest project, "World of Text," began with a simple yet powerful concept: capture everyday text inputs and transform them into enriched, actionable data. The idea was to create a system that could seamlessly integrate with various tools and services, adding value to basic text entries by enhancing them with context and storing them for future use.
The Concept
The core idea behind "World of Text" is to take any textual input, whether it’s a note about a home improvement project, an idea for automation, or a simple to-do list item, and automatically enrich it with additional context. This enriched data is then stored in a database and converted into actionable tasks, making it easy to manage and track through various stages of completion.
Technologies Used
To bring this concept to life, I leveraged several technologies, each serving a specific purpose within the system:
Proxmox: The virtualization platform that hosts all the different components of the system. Proxmox enables the creation and management of virtual machines and containers, allowing for a flexible and scalable environment to run services like Node-RED, MongoDB, and Nginx.
Node-RED: The flow-based development tool used to orchestrate the entire process. Node-RED handles everything from capturing user inputs to processing and enriching the data, and finally, integrating with other services.
MongoDB: A NoSQL database used to store the enriched text entries. MongoDB’s flexibility in handling JSON-like documents makes it an ideal choice for storing the varied and dynamic data generated by the system.
Todoist: The task management tool that transforms enriched text entries into actionable tasks. By integrating Todoist with Node-RED, I can automatically generate tasks that are prioritized and labeled according to the context provided by the enriched data.
Nginx: A web server used as a reverse proxy to manage and secure access to the Node-RED instance. Nginx ensures that the system is both accessible and protected from unauthorized access.
Cloudflare: A DNS and security service that provides SSL certificates and other security features, ensuring that the system remains secure and performant. Cloudflare also helps manage DNS settings, making the system accessible through a custom domain.
AI-Powered Intent Processing: To enrich the text data, I used AI-driven intent processing within Node-RED. This component adds valuable context to the raw text inputs, making them more meaningful and actionable.
iOS Shortcuts: This automation tool on iOS devices is used to seamlessly interact with the system, particularly for sending data to the Node-RED API endpoint. Through a custom shortcut, users can input text directly on their iOS device, which is then sent to the system for processing. The shortcut handles tasks like generating the appropriate POST request, adding authorization headers, and sending user input, making the interaction with the backend system smooth and efficient directly from a mobile device.
Each of these technologies plays a crucial role in the overall system, working together to turn simple text inputs into valuable, organized data that can be acted upon with minimal effort.
Technology deployoment
Proxmox
Purpose: Hosting the virtual machines (VMs) and containers that run the various components of the system.
Requirements:
Installation of Proxmox: Proxmox must be installed on a physical server or a dedicated machine. It needs to be accessible through your network, and a basic understanding of managing virtual environments is required.
Network Configuration: Ensure that Proxmox is configured with a static IP address, and has proper network settings to allow communication between VMs and containers.
Virtual Machine/Container Creation: Set up VMs or containers for each of the components like Node-RED, MongoDB, and Nginx. Allocate sufficient resources (CPU, RAM, and storage) based on the needs of each component.
References:
Node-RED
Purpose: Orchestrating the data flow, handling inputs, processing, and integration with other services.
Requirements:
Installation on a VM/Container: Install Node-RED on a dedicated VM or container within Proxmox.
Node-RED Modules: Install necessary Node-RED modules like
node-red-contrib-mongodb
for MongoDB integration,node-red-contrib-ai-intent
for AI-based text enrichment, andnode-red-contrib-todoist-api
for Todoist integration.Environment Configuration: Configure Node-RED to ensure it can communicate with MongoDB and Todoist. This includes setting up environment variables or configuration nodes within Node-RED to store API keys, database connection strings, and other credentials.
References:
MongoDB
Purpose: Storing the enriched text entries and context data.
Requirements:
Installation on a VM/Container: Set up MongoDB on its own VM or container within Proxmox. Ensure that MongoDB is configured to allow connections from Node-RED.
Database Configuration: Create a database (
worldOfText
for instance) and necessary collections to store the enriched data.User and Access Control: Set up a MongoDB user with appropriate permissions that Node-RED will use to interact with the database. Ensure network access is allowed for the Node-RED container/VM.
References:
Todoist
Purpose: Converting enriched text entries into actionable tasks.
Requirements:
API Access: Register for a Todoist account and generate an API token. This token will be used within Node-RED to interact with Todoist.
Project Setup: Create a project in Todoist where the tasks will be added. Note the project ID as it will be needed in Node-RED.
Node-RED Configuration: Configure the
node-red-contrib-todoist-api
module with your Todoist API token and project ID to allow Node-RED to create tasks based on the enriched data.
References:
Nginx Reverse proxy Manager
Purpose: Acting as a reverse proxy to secure and manage access to the Node-RED instance.
Requirements:
Installation on a VM/Container: Install Nginx on a dedicated VM or container within Proxmox.
Reverse Proxy Configuration: Set up Nginx to reverse proxy the Node-RED instance, ensuring it can be accessed securely from outside your network.
SSL Configuration: Obtain and install SSL certificates (using Let's Encrypt or similar) to secure the connection to Node-RED. Configure Nginx to enforce HTTPS connections.
Firewall Rules: Ensure that the firewall is configured to allow traffic on the ports used by Nginx (typically 80 and 443).
References:
Cloudflare
Purpose: Providing DNS management, SSL certificates, and additional security for the system.
Requirements:
Domain Setup: Register a domain name and configure Cloudflare as your DNS provider.
DNS Configuration: Set up DNS records in Cloudflare to point to your Nginx server’s public IP address.
SSL/TLS Configuration: Use Cloudflare to generate and manage SSL certificates. Ensure SSL is configured to “Full (strict)” mode for end-to-end encryption between users and your Nginx server.
Security Settings: Configure additional security settings in Cloudflare such as DDoS protection, rate limiting, and firewall rules.
References:
AI-Powered Intent Processing in Node-RED
Purpose: Enriching raw text inputs with additional context to make them actionable.
Requirements:
Node-RED Module Installation: Install and configure the
node-red-contrib-ai-intent
module within Node-RED.API Integration: Set up any required API keys or connections needed by the AI module to process text inputs.
Flow Design: Create Node-RED flows that utilize the AI intent processing to enrich data before storing it in MongoDB or sending it to Todoist.
References:
iOS Shortcuts
Purpose: Facilitating quick and seamless interaction with the Node-RED API directly from iOS devices, allowing users to send inputs and retrieve data without needing to access a computer.
Requirements:
Shortcut Creation: Use the Shortcuts app on iOS to create a custom shortcut that sends a POST request to the Node-RED API. The shortcut should include input fields for the user’s message, resident identifier, and tags.
API Configuration: Configure the shortcut with the appropriate API endpoint URL and include authorization headers.
Testing and Validation: Test the shortcut to ensure it correctly sends data to Node-RED and handles responses appropriately. References:
OpenAI
Purpose: Enriching text inputs with context and generating intelligent responses through AI-powered processing.
Requirements:
API Access: Register for OpenAI API access and obtain an API key.
Node-RED Integration: Install and configure the necessary Node-RED nodes (e.g.,
node-red-node-openai
) to communicate with the OpenAI API.Flow Design: Create Node-RED flows that leverage OpenAI to analyze and enrich input data before it is stored in MongoDB or converted into tasks in Todoist.
Rate Limits: Be aware of and manage OpenAI API rate limits to ensure smooth operation without service interruptions. References:
Setup of outside access into Node-RED
Let's walk through the process of securing access to Node-RED setup using Cloudflare with WAF rules, NGINX as a reverse proxy, and an HTTP In node with a switch node for authorization checks based on headers.
Setting Up Cloudflare with Web Application Firewall (WAF) Rules
Purpose: Cloudflare provides an additional layer of security by managing DNS, SSL/TLS encryption, and acting as a firewall to block malicious traffic before it even reaches your server.
Steps:
DNS Configuration:
Point your domain name to the public IP address of your NGINX server via an A or CNAME record in Cloudflare's DNS settings.
Enable the Cloudflare proxy (orange cloud icon) to route all traffic through Cloudflare's network.
SSL/TLS Configuration:
Set the SSL/TLS encryption mode in Cloudflare to "Full (strict)" to ensure encrypted communication between Cloudflare and your NGINX server.
Optionally, use a Cloudflare Origin Certificate on your NGINX server for added security.
WAF Rules Configuration:
Navigate to Firewall > Firewall Rules in Cloudflare.
Create rules to block or challenge common threats, such as SQL injections or cross-site scripting (XSS).
You can also create a rule to allow traffic only from specific IP addresses (e.g., your home or office network) to further restrict access.
References:
Configuring NGINX as a Reverse Proxy
Purpose: NGINX acts as a secure reverse proxy, forwarding requests from the internet to your Node-RED instance running on an internal port.
Steps:
Setting Up NGINX Reverse Proxy:
Install NGINX on a VM or container in Proxmox.
Edit the NGINX configuration file (typically found at
/etc/nginx/sites-available/default
) to include a server block for your domain:
Enable the configuration by creating a symbolic link to
sites-enabled
and reload NGINX:
Configuring SSL:
Use Certbot to obtain an SSL certificate:
Update the NGINX configuration to redirect HTTP traffic to HTTPS:
References:
Securing Node-RED Access with HTTP In Node and Authorization Using a Switch Node
Purpose: The Node-RED HTTP In node handles incoming requests, while a switch node checks the authorization headers to ensure that only authorized users can interact with your flows.
Steps:
Setting Up the HTTP In Node:
In Node-RED, create a new flow and add an HTTP In node. Configure the node with the desired method (e.g., POST) and path (e.g.,
/webhook
).
Implementing Authorization with a Switch Node:
After the HTTP In node, add a
function
node to extract the authorization header:
Next, add a
switch
node to validate the extracted authorization token. Configure the switch node with rules to check if themsg.authHeader
matches the expected token:Example switch node setup:
msg.authHeader == "Bearer your-secure-token"
(If this condition is true, the flow continues).Otherwise, the request is rejected.
Following the switch node, add two outputs:
Output 1: Connect to the nodes that handle the authorized request (e.g., a processing function and then an HTTP Response node).
Output 2: Connect to a
change
node that sets a response message indicating unauthorized access and then to an HTTP Response node with a401 Unauthorized
status.
Securing Node-RED Itself:
Set up user-based authentication in Node-RED by editing the
settings.js
file (usually found in the~/.node-red
directory):
Use
bcrypt
to hash your password before adding it to the configuration file.
References:
Summary
By combining Cloudflare's WAF for filtering traffic, NGINX as a reverse proxy for handling incoming connections securely, and Node-RED with an HTTP In node secured via a switch node for authorization checks, you create a highly secure environment for your Node-RED setup. This ensures that only requests with the correct authorization headers are allowed to proceed, keeping your flows and data safe.
This structured approach not only secures the network and web layers but also ensures that any interaction with your Node-RED environment is tightly controlled and monitored.
Node-RED data handling - preparing the data for OpenAI
Let’s dive into the “ API POST” group of the “WorldOfText” flow in Node-RED. This part of the flow is critical as it prepares the data for submission to OpenAI, ensuring the input is processed, checked, and formatted correctly before sending it out.
Step-by-Step Breakdown
1. HTTP In Node
Name: [post]
/api/apiEndpoint
Purpose: Receives incoming HTTP POST requests directed to the
/api/apiEndpoint
endpoint.Role: This node acts as the entry point for the flow, where the raw data is received.
2. Function Node: "Authz validation"
Purpose: Validates the authorization header to ensure that the request is from an authorized source.
JavaScript Code:
Outputs:
Output 1: If authorization is successful, the flow continues.
Output 2: If authorization fails, the flow routes to the "Unauthorized" node.
Role: This node ensures that only requests with the correct authorization token can proceed further.
3. Function Node: "Unauthorized"
Purpose: Handles cases where authorization fails.
JavaScript Code:
Role: Sends a "401 Unauthorized" response back to the client if the authorization check fails.
4. Function Node: "response cleaner"
Purpose: Prepares the data received from the OpenAI API for further processing.
JavaScript Code:
Role: Extracts and cleans the AI-generated response for use in the system.
5. Function Node: "systemPrompt"
Purpose: Adds the system prompt details to the payload for further processing.
JavaScript Code:
Role: This node formats and prepares the system prompt and original message before combining it with other data.
6. Join Node
Purpose: Combines the outputs of the "response cleaner" and "systemPrompt" nodes.
Configuration: Joins messages into a single message object.
Role: Merges the cleaned response and system prompt into one message payload.
7. Function Node: "combined payload"
Purpose: Prepares the final payload combining all necessary information before storing it in MongoDB and returning a response.
JavaScript Code:
Role: This node structures the final payload for the MongoDB insertion.
Purpose of the SystemPrompt Node
The "systemPrompt" node is designed to augment the incoming data with additional context, ensuring that the data has the necessary metadata and structure required for downstream processing, particularly when interacting with AI models or databases. This node essentially creates a structured prompt that provides the AI with the context it needs to generate accurate and relevant responses.
Detailed Functionality
Input Data Handling:
The node receives the processed message payload from the previous node in the flow. This payload typically contains the raw input data that was submitted to the
/api/apiEndpoint
endpoint.This raw data might include user-provided text, tags, and any other metadata that was sent with the HTTP POST request.
System Prompt Construction:
The node constructs a system prompt that encapsulates the core task or directive for the AI model. This prompt provides the necessary context and instructions that guide the AI's response generation.
The prompt includes information such as:
Assistant Role: Specifies the role of the AI, in this case, as a "Cognitive AI Assistant".
Task Description: Clearly outlines the task the AI is expected to perform, such as generating context and metadata for the input data within the "World of Text" database.
Core Responsibilities and Objectives: Defines what the AI should focus on, including data integrity, security, and relevance, as well as dynamically connecting related concepts and tasks.
Input Data: The actual text or message that the AI needs to process, ensuring that the AI has all the necessary information to produce a relevant response.
Formatting the Prompt:
The node formats the prompt in a specific structure, making it easy to parse and use by downstream nodes or external APIs.
The prompt is embedded into a JSON structure, which might look something like this:
Output for Further Processing:
After constructing the system prompt, the node outputs this structured data to be combined with other pieces of information (such as AI responses) in the subsequent nodes.
The system prompt and the original message are passed downstream, often to a node that will send this data to an AI service like OpenAI for processing.
Why is the SystemPrompt Node Important?
Contextual Accuracy: The system prompt provides essential context, ensuring that the AI model understands the task at hand and generates responses that are aligned with the specific needs of the "World of Text" system.
Consistency in Responses: By standardizing the prompt structure, the node helps ensure that the AI's output is consistent, relevant, and formatted correctly for storage in the MongoDB database.
Enhanced AI Interaction: The node facilitates better interaction with the AI model by clearly defining its role and the expectations for its output, which leads to more meaningful and actionable AI-generated content.
The current SystemPrompt used:
In Summary
The "systemPrompt" node is a vital component in the flow that sets the stage for effective AI processing. It ensures that the data being sent to the AI is well-organized, contextually rich, and aligned with the system's objectives, ultimately leading to better data handling, storage, and retrieval in the "World of Text" system.
OpenAI Prompt Engine Flow: Detailed Breakdown
The "OpenAI Prompt Engine" is an essential component of the WorldOfText system that processes user input through OpenAI's GPT model. It combines user messages with system instructions to generate meaningful responses. Below is a step-by-step explanation of the flow, including the function of each node and the JavaScript code where applicable.
1. Brain LinkIn OpenAI (Link In Node)
Purpose: This node serves as the entry point for data into the "OpenAI prompt engine." It receives input from other parts of the Node-RED environment via a "link" connection.
Flow: The data received is passed to the "User" node.
2. User (OpenAI User Node)
Content:
{payload.originalMessage}
Purpose: This node represents the user's input in the OpenAI conversation. It extracts the user's original message from the payload and prepares it to be sent along with system instructions to the AI model.
Flow: The user's message is passed to the "System" node, which will combine it with the system-generated prompts.
3. System (OpenAI System Node)
Instruction:
{payload.systemPrompt}
Purpose: This node adds system-generated instructions to the user's input, creating a structured prompt that will guide the AI model in generating an appropriate response.
Flow: The combined user input and system instructions are sent to the node for processing by the OpenAI API.
4. OpenAI Chat Node
Configuration:
Tool Choice: Auto
Model:
gpt-4o-mini
Temperature: 0.7
Max Tokens: 1200
Top P: 1
Frequency Penalty: 0
Presence Penalty: 0
Purpose: This is the core node that interacts with the OpenAI API to generate a response based on the combined user input and system prompt.
Flow: The response generated by the OpenAI API is sent to debug nodes for logging and further processing.
5. Brain LinkOut (Link Out Node)
Purpose: This node outputs the generated AI response to other parts of the Node-RED environment via a "link" connection, allowing the flow to continue its processing.
This section illustrates how the "OpenAI Prompt Engine" integrates user input with system prompts to generate responses using OpenAI's GPT model. Each node plays a specific role in processing and refining the input to produce meaningful, context-aware outputs.
MongoDB Create and Response Return Flow: Detailed Breakdown
The "MongoDB Create and Response Return" flow is designed to handle the storage of data in MongoDB and return the appropriate HTTP response based on authorization and the processing of data. Below is a detailed explanation of each node, including the function and JavaScript code where applicable.
1. WOT linkIn (Link In Node)
Purpose: This node acts as the entry point for the flow, receiving data from the "API POST" section of the flow. It triggers the processing of data within this part of the flow.
Flow: The data received is passed to the "extract data" function node.
2. Extract Data (Function Node)
JavaScript Code:
Purpose: This node extracts the
textResponse
andmongoData
from the nested JSON structure provided by the OpenAI response. The node outputs these two parts separately to allow for different subsequent processing.Flow: The
textResponse
is passed to the "response combiner" function node, andmongoData
is sent to the MongoDB node and the Todoist task creation flow.
3. Response Combiner (Function Node)
JavaScript Code:
Purpose: This node prepares the final response by combining the
textResponse
with the HTTP request and response objects stored in the global context. This combined message is used to send the appropriate HTTP response.Flow: The combined message is sent to the "Good authorization" HTTP response node.
4. MongoDB-WOT wot (MongoDB Out Node)
Configuration:
Collection:
wot
Operation: Insert
Purpose: This node inserts the
mongoData
extracted from the previous function node into the MongoDB database. It ensures that the processed data is stored in the correct collection.Flow: Data is inserted into the MongoDB collection, and no further output is required from this node.
The output in the database in MongoDB will look like this:
This is an example stored item
5. Switch (Switch Node)
Purpose: This node checks the tags present in the
mongoData
payload and directs the flow to the appropriate processing path based on the tag value (e.g., "todo", "project", "idea").Flow: Depending on the tags, the flow is directed to one of three function nodes: "todo," "project," or "idea."
6. Todo, Project, Idea (Function Nodes)
Each of these nodes processes the mongoData
to create tasks in Todoist. The structure and purpose of these nodes are similar, with slight variations to handle different types of tasks.
JavaScript Code Example for Todo:
Purpose: These nodes transform the
mongoData
into a format suitable for creating tasks in Todoist. Each node handles a specific type of task based on the tags in themongoData
.Flow: The transformed task object is sent to the Todoist task creation node.
7. Create Task (Todoist Task Create Node)
Purpose: This node sends the task created in the previous function nodes to Todoist, ensuring that tasks are created based on the user's input.
Flow: The task is created in Todoist, and the flow completes its execution.
8. Good Authorization (HTTP Response Node)
Configuration:
Status Code: 200
Purpose: This node sends an HTTP 200 response back to the client, indicating that the request was successfully processed and the data was stored in MongoDB.
Flow: The flow ends after this node sends the response.
This section describes the complete process of receiving data, validating it, processing it, storing it in MongoDB, and creating tasks in Todoist, along with returning the appropriate HTTP response. The flow is designed to handle different types of data and ensures that each type is processed accordingly.
Integrating iOS Shortcuts into the World of Text Project
The use of iOS Shortcuts in this project is critical for providing quick and easy access to the World of Text (WoT) database directly from a mobile device. Here’s a step-by-step breakdown of how the Shortcut works and why it’s relevant:
Ask for Input:
The Shortcut begins by prompting the user to enter text. This input will serve as the main content (or "message") to be sent to the WoT API. This ensures that users can capture their thoughts or notes quickly, directly on their device.
POST Request Setup:
After receiving the user input, the Shortcut configures a POST request to be sent to the WoT API. Key elements include:
URL: The API endpoint (
https://URL/api/apiEndpoint
) is predefined.Authorization: A secure token (
Bearer your-secure-token
) is included in the headers to ensure that only authorized users can make requests.Request Body: This includes the user’s
message
, a predefinedresident
name (e.g., "HenkJan"), and atag
(which can be selected from a predefined list). The tag helps categorize the note for easier retrieval later.
Sending the Request:
Once everything is set, the Shortcut sends the POST request. The data is then transmitted securely to the WoT API, where it is processed and stored in the database. This step ensures that user input is captured and saved immediately, reducing the risk of losing important thoughts or ideas.
Get and Speak the Response:
After the data is sent, the Shortcut retrieves the response from the API. This could be a confirmation message or any other relevant feedback. The Shortcut uses the "Speak" action to read out this response aloud to the user, providing immediate confirmation that their input has been successfully processed.
Why is This Important?
Seamless Integration: This setup allows users to interact with the WoT system without needing to access a desktop or open multiple apps. Everything is handled in one smooth process on their mobile device.
Efficiency: The entire workflow is automated, ensuring that ideas are captured quickly and processed without delay.
Mobility: Users can input data into the WoT database from anywhere, ensuring that important notes are never missed.
This iOS Shortcut setup is a perfect example of how mobile technology can enhance and extend the functionality of a complex system like the World of Text, making it more accessible and user-friendly.
Future Enhancements and To-Dos
As the "World of Text" project evolves, there are several key features that I plan to implement to further enhance its functionality. One of the primary goals is to introduce the ability to revisit and continue working on existing data entries within the databases, such as Todoist or MongoDB.
Planned Features:
Dynamic Data Continuation:
Objective: Enable the system to retrieve and interact with existing entries.
Implementation: This would involve developing new functionality that allows users to fetch a specific entry from Todoist or MongoDB and continue updating or interacting with it. For instance, you could retrieve a task from Todoist and add more details, update its status, or link it to new contextual information without starting from scratch.
Enhanced API Interactions:
Objective: Provide more robust and flexible API interactions to support the continuation of data processes.
Implementation: This requires extending the current API interactions in Node-RED to support not just the creation of new entries but also the modification of existing ones. The workflow would need to include steps for selecting, retrieving, and updating entries based on user input or system prompts.
User-Driven Workflow Enhancements:
Objective: Allow users to dictate how data should be processed in subsequent interactions.
Implementation: Integrate new nodes or functions that give users the option to choose whether to create a new entry or continue working on an existing one. This might involve implementing conditional logic based on user prompts or adding more complex decision-making capabilities within the flow.
Advanced Search and Retrieval:
Objective: Improve the search functionality to allow users to easily find and retrieve relevant data from the databases.
Implementation: Enhance the search capabilities within the databases to allow for more refined queries. This could include searching by tags, content, or other metadata, enabling users to quickly locate specific entries they wish to continue working on.
These enhancements will significantly increase the flexibility and utility of the "World of Text" system, transforming it from a simple data entry tool into a dynamic, iterative platform that supports ongoing project management and data interaction.
Future Enhancements: Integrating Obsidian and Zettelkasten
Obsidian Integration The potential addition of Obsidian into this project opens up advanced capabilities for managing and organizing ideas. Obsidian is a powerful note-taking tool that excels in creating a knowledge network, making it ideal for handling the intricate web of ideas and tasks generated by the system. By integrating Obsidian, users can seamlessly sync their ideas with the World of Text database, ensuring that all data is accessible and interconnected.
Zettelkasten Method Obsidian's strength lies in its support for the Zettelkasten method, a note-taking technique designed to create a dynamic, interconnected web of knowledge. Each note is treated as a distinct, linkable idea, fostering a deeper understanding and exploration of complex concepts. By implementing the Zettelkasten method within this system, users can not only store but also actively connect and expand upon their ideas, making it a valuable addition to the project.
Potential Use Cases:
Knowledge Management: Organize and link ideas stored in MongoDB with Obsidian's Zettelkasten, enabling more structured and retrievable information.
Idea Expansion: Use the Zettelkasten method to explore and develop ideas further, turning simple notes into complex, interconnected networks of thought.
Cross-Platform Sync: Seamlessly sync notes and ideas between Obsidian and the World of Text database, ensuring consistent data management across platforms.
Next Steps: To implement Obsidian and the Zettelkasten method, additional coding and API integrations will be required. This will involve setting up data synchronization between Obsidian and the existing system, as well as creating workflows that support the Zettelkasten approach within the broader project architecture.
Last updated