├── src ├── modules │ ├── __init__.py │ ├── config_loader.py │ ├── trello_api.py │ ├── utilities.py │ ├── board_operations.py │ └── card_operations.py └── main.py ├── imgs ├── api_key.png ├── cards │ ├── Queue.png │ ├── Stack.png │ ├── Trie.png │ ├── DP - 1D.png │ ├── Intervals.png │ ├── Prefix Sum.png │ ├── Backtracking.png │ ├── Binary Search.png │ ├── Graphs - BFS.jpg │ ├── Graphs - DFS.png │ ├── Linked List.png │ ├── Two Pointers.png │ ├── Monotonic Stack.png │ ├── Sliding Window.png │ ├── Arrays and Strings.png │ ├── Binary Search Tree.png │ ├── Binary Tree - BFS.png │ ├── Binary Tree - DFS.png │ ├── Bit Manipulation.png │ ├── Hash Map and Set.png │ ├── DP - Multidimensional.png │ └── Heap and Priority Queue.png ├── trello_board.png └── background │ └── groot.png ├── config ├── settings.ini ├── comment.md └── leetcode75.json ├── LICENSE ├── .github └── workflows │ └── run_trello_script.yml └── README.md /src/modules/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /imgs/api_key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/api_key.png -------------------------------------------------------------------------------- /imgs/cards/Queue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/cards/Queue.png -------------------------------------------------------------------------------- /imgs/cards/Stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/cards/Stack.png -------------------------------------------------------------------------------- /imgs/cards/Trie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/cards/Trie.png -------------------------------------------------------------------------------- /imgs/cards/DP - 1D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/cards/DP - 1D.png -------------------------------------------------------------------------------- /imgs/trello_board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/trello_board.png -------------------------------------------------------------------------------- /imgs/background/groot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/background/groot.png -------------------------------------------------------------------------------- /imgs/cards/Intervals.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/cards/Intervals.png -------------------------------------------------------------------------------- /imgs/cards/Prefix Sum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/cards/Prefix Sum.png -------------------------------------------------------------------------------- /imgs/cards/Backtracking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/cards/Backtracking.png -------------------------------------------------------------------------------- /imgs/cards/Binary Search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/cards/Binary Search.png -------------------------------------------------------------------------------- /imgs/cards/Graphs - BFS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/cards/Graphs - BFS.jpg -------------------------------------------------------------------------------- /imgs/cards/Graphs - DFS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/cards/Graphs - DFS.png -------------------------------------------------------------------------------- /imgs/cards/Linked List.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/cards/Linked List.png -------------------------------------------------------------------------------- /imgs/cards/Two Pointers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/cards/Two Pointers.png -------------------------------------------------------------------------------- /imgs/cards/Monotonic Stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/cards/Monotonic Stack.png -------------------------------------------------------------------------------- /imgs/cards/Sliding Window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/cards/Sliding Window.png -------------------------------------------------------------------------------- /imgs/cards/Arrays and Strings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/cards/Arrays and Strings.png -------------------------------------------------------------------------------- /imgs/cards/Binary Search Tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/cards/Binary Search Tree.png -------------------------------------------------------------------------------- /imgs/cards/Binary Tree - BFS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/cards/Binary Tree - BFS.png -------------------------------------------------------------------------------- /imgs/cards/Binary Tree - DFS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/cards/Binary Tree - DFS.png -------------------------------------------------------------------------------- /imgs/cards/Bit Manipulation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/cards/Bit Manipulation.png -------------------------------------------------------------------------------- /imgs/cards/Hash Map and Set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/cards/Hash Map and Set.png -------------------------------------------------------------------------------- /imgs/cards/DP - Multidimensional.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/cards/DP - Multidimensional.png -------------------------------------------------------------------------------- /imgs/cards/Heap and Priority Queue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrannyProgramming/trello-leetcoing75-scheduler/HEAD/imgs/cards/Heap and Priority Queue.png -------------------------------------------------------------------------------- /config/settings.ini: -------------------------------------------------------------------------------- 1 | [TRELLO] 2 | BASE_URL = https://api.trello.com/1 3 | BOARD_NAME = LeetCode 75 Challenge 4 | 5 | [LISTS] 6 | DEFAULTS = To Do, Doing, Done 7 | REQUIRED = Completed, Retrospective, Do this week, Backlog 8 | 9 | [WEEK] 10 | START_DAY = 0 11 | END_DAY = 4 12 | WORKDAYS = 5 13 | 14 | [LABELS] 15 | DEFAULT_COLORS = Easy:green, Medium:yellow, Somewhat know:orange_light, Do not know:red_light, Know:lime 16 | 17 | [PROBLEMS] 18 | PROBLEMS_PER_DAY = 1 19 | 20 | [FILES] 21 | COMMENT_MD_PATH = ./config/comment.md -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Alex McGonigle 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/run_trello_script.yml: -------------------------------------------------------------------------------- 1 | name: Run Trello Script 2 | 3 | on: 4 | schedule: 5 | # This will run at 01:00 UTC every Monday 6 | - cron: '0 1 * * MON' 7 | workflow_dispatch: # This allows manual triggering 8 | 9 | jobs: 10 | run_script: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v2 16 | 17 | - name: Set up Python 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: '3.x' 21 | 22 | - name: Install dependencies 23 | run: | 24 | python -m pip install --upgrade pip 25 | pip install requests 26 | 27 | - name: Setup Base URL for Images 28 | run: | 29 | # Construct the base raw URL based on the repository structure 30 | RAW_URL_BASE="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/main/" 31 | echo "RAW_URL_BASE=$RAW_URL_BASE" >> $GITHUB_ENV 32 | 33 | - name: Run Trello script 34 | run: python src/main.py 35 | env: 36 | API_KEY: ${{ secrets.API_KEY }} 37 | OAUTH_TOKEN: ${{ secrets.OAUTH_TOKEN }} 38 | RAW_URL_BASE: ${{ env.RAW_URL_BASE }} 39 | TOPICS_JSON_PATH: './config/leetcode75.json' -------------------------------------------------------------------------------- /config/comment.md: -------------------------------------------------------------------------------- 1 | ## Understanding the Problem: 2 | 3 | **What is the main problem statement?** 4 | **Are there any constraints or assumptions made in the problem?** 5 | **What are the input and output formats?** 6 | 7 | ## Approach & Strategy: 8 | 9 | **What is the main logic or idea behind your solution?** 10 | **Which data structures did you use and why?** 11 | **Did you consider multiple approaches?** If so, what made you choose one over the other? 12 | **How does the chosen approach handle edge cases?** 13 | 14 | ## Complexity Analysis: 15 | 16 | **What is the time complexity of your solution?** Can you explain why? 17 | **What is the space complexity of your solution?** Can you explain why? 18 | **Is there any potential for optimization to reduce either time or space complexity?** 19 | 20 | ## Implementation: 21 | 22 | **Were there any challenges you faced while coding the solution?** 23 | **Did you use any built-in functions or libraries?** Why did you choose them? 24 | **Are there any specific lines of code or constructs that could be more efficient or cleaner?** 25 | 26 | ## Testing & Edge Cases: 27 | 28 | **How did you test your solution?** 29 | **Did you consider all possible edge cases?** 30 | **Were there any test cases that your solution failed initially?** How did you resolve them? 31 | 32 | ## Learning & Concepts: 33 | 34 | **Were there any new algorithms or data structures you learned from this problem?** 35 | **How does this problem relate to other problems you've seen?** 36 | **Can you generalize the strategy used in this problem to other types of problems?** 37 | 38 | ## Comparative Analysis: 39 | 40 | **How does your solution compare to other solutions in terms of efficiency and simplicity?** 41 | **Are there solutions that use a completely different approach to solve the problem?** If so, how do they work? 42 | 43 | ## Future Considerations: 44 | 45 | **Are there any extensions or variations to this problem that you can think of?** 46 | **How would you adapt your solution to handle these variations?** 47 | **Is there anything you would do differently if you were to solve this problem again?** 48 | -------------------------------------------------------------------------------- /src/modules/config_loader.py: -------------------------------------------------------------------------------- 1 | """ 2 | Configuration Loading Module. 3 | 4 | This module provides utility functions to facilitate loading configuration and settings 5 | for interacting with the Trello API. Configuration and settings can be loaded from both 6 | INI files and environment variables. 7 | 8 | Functions: 9 | - load_ini_settings: Loads settings from an INI file and returns them as a dictionary. 10 | - load_config: Loads API and other related configurations from environment variables. 11 | 12 | Dependencies: 13 | - os: Used to access environment variables. 14 | - configparser: Used to parse INI configuration files. 15 | 16 | Usage: 17 | Ensure that the required settings are available in ".config/settings.ini" for `load_ini_settings` 18 | and the required environment variables are set for `load_config` before invoking these functions. 19 | 20 | Note: 21 | - Keep the ".config/settings.ini" file secure as it might contain sensitive settings. 22 | - Ensure environment variables such as API_KEY, OAUTH_TOKEN, RAW_URL_BASE, and TOPICS_JSON_PATH 23 | are set before using the `load_config` function. 24 | 25 | Author: Alex McGonigle @grannyprogramming 26 | """ 27 | 28 | 29 | import os 30 | import configparser 31 | 32 | 33 | def load_ini_settings(): 34 | """ 35 | Load application-specific settings from an INI file. 36 | """ 37 | config = configparser.ConfigParser() 38 | config.read("config/settings.ini") 39 | 40 | return { 41 | "BASE_URL": config["TRELLO"]["BASE_URL"], 42 | "BOARD_NAME": config["TRELLO"]["BOARD_NAME"], 43 | "DEFAULT_LISTS": config["LISTS"]["DEFAULTS"].split(", "), 44 | "REQUIRED_LISTS": config["LISTS"]["REQUIRED"].split(", "), 45 | "START_DAY": int(config["WEEK"]["START_DAY"]), 46 | "END_DAY": int(config["WEEK"]["END_DAY"]), 47 | "WORKDAYS": int(config["WEEK"]["WORKDAYS"]), 48 | "DEFAULT_LABELS_COLORS": dict( 49 | item.split(":") for item in config["LABELS"]["DEFAULT_COLORS"].split(", ") 50 | ), 51 | "PROBLEMS_PER_DAY": int(config["PROBLEMS"]["PROBLEMS_PER_DAY"]), 52 | "COMMENT_MD_PATH": config["FILES"]["COMMENT_MD_PATH"], 53 | } 54 | 55 | 56 | def load_config(): 57 | """ 58 | Load essential configurations from environment variables. 59 | """ 60 | return { 61 | "API_KEY": os.environ.get("API_KEY"), 62 | "OAUTH_TOKEN": os.environ.get("OAUTH_TOKEN"), 63 | "RAW_URL_BASE": os.environ.get("RAW_URL_BASE"), 64 | "TOPICS_JSON_PATH": os.environ.get("TOPICS_JSON_PATH"), 65 | } 66 | -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | """ 2 | Main script for Trello Card Management. 3 | 4 | This script coordinates the primary operations for managing Trello cards, including setting up the Trello board, 5 | processing LeetCode problems, and managing card retests. The script leverages various utility functions from 6 | imported modules for specific tasks. 7 | 8 | Functions: 9 | - setup_trello_board: Set up the Trello board by ensuring the necessary lists and labels exist. 10 | - process_cards: Process all problem cards for the board, populate the "To Do this Week" list, and manage card retests. 11 | - main: The main function that coordinates the primary operations. 12 | 13 | Usage: 14 | This script is intended to be run as the main entry point. It should be executed after the necessary environment 15 | variables and configurations are set. 16 | 17 | Author: Alex McGonigle @grannyprogramming 18 | """ 19 | 20 | import logging 21 | import json 22 | import os 23 | from datetime import datetime 24 | from modules.config_loader import load_ini_settings, load_config 25 | from modules.board_operations import ( 26 | get_board_id, 27 | set_board_background_image, 28 | manage_board_lists, 29 | create_missing_labels, 30 | ) 31 | from modules.card_operations import ( 32 | manage_this_week_list, 33 | retest_cards, 34 | process_all_problem_cards, 35 | ) 36 | 37 | logging.basicConfig( 38 | level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" 39 | ) 40 | 41 | 42 | def setup_trello_board(config, settings): 43 | """ 44 | Set up the Trello board for operations. 45 | 46 | Ensures the board exists, sets its background image, manages its lists, and creates any missing labels. 47 | """ 48 | board_id = get_board_id(config, settings, settings["BOARD_NAME"]) 49 | if board_id is None: 50 | logging.error("Failed to retrieve board ID.") 51 | return None 52 | set_board_background_image(board_id) 53 | manage_board_lists(board_id) 54 | create_missing_labels(board_id) 55 | return board_id 56 | 57 | 58 | def process_cards(config, settings, board_id, topics, current_date): 59 | """ 60 | Process Trello cards for a given board and date. 61 | 62 | Processes all problem cards, populates the "To Do this Week" list, and manages card retests. 63 | """ 64 | process_all_problem_cards(config, settings, board_id, topics, current_date) 65 | manage_this_week_list(config, settings, board_id) 66 | retest_cards(config, settings, settings["BOARD_NAME"], current_date) 67 | 68 | 69 | def main(): 70 | """ 71 | The main function that coordinates the primary operations. 72 | 73 | Loads configurations, sets up the Trello board, and processes the cards. 74 | """ 75 | config = load_config() 76 | settings = load_ini_settings() 77 | 78 | # Load topics from the JSON file 79 | topics_path = os.environ.get("TOPICS_JSON_PATH") 80 | with open(topics_path, "r", encoding="utf-8") as file: 81 | topics = json.load(file) 82 | 83 | current_date = datetime.now() 84 | 85 | # Set up the Trello board 86 | board_id = setup_trello_board(config, settings) 87 | if not board_id: 88 | exit(1) 89 | 90 | process_cards(config, settings, board_id, topics, current_date) 91 | 92 | logging.info("Main script execution completed!") 93 | 94 | 95 | if __name__ == "__main__": 96 | main() 97 | -------------------------------------------------------------------------------- /src/modules/trello_api.py: -------------------------------------------------------------------------------- 1 | """ 2 | Trello API Module. 3 | 4 | This module provides utility functions to interact with the Trello API. It encompasses 5 | a variety of functions for sending requests, constructing URLs, downloading images, and more. 6 | 7 | Functions: 8 | - make_request(url, method, params=None, data=None, timeout=None, files=None): Sends a request to a specified URL and handles exceptions and logging. 9 | - trello_request(_config, _settings, resource, method="GET", entity="boards", timeout=None, files=None, **kwargs): Sends a request to the Trello API using specified _configurations and _settings. 10 | - construct_url(base_url, entity, resource, **kwargs): Constructs a URL for the Trello API based on provided parameters. 11 | - download_image(url, filepath="tmp_image.png"): Downloads an image from a given URL and saves it to a specific path. 12 | - fetch_cards_from_list(_config, _settings, list_id): Fetches all cards from a given Trello list. 13 | 14 | Dependencies: 15 | - logging: Used for logging information and error messages. 16 | - requests: Used for making HTTP requests. 17 | 18 | Author: Alex McGonigle @grannyprogramming 19 | """ 20 | 21 | import logging 22 | import requests 23 | 24 | 25 | logging.basicConfig( 26 | level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" 27 | ) 28 | 29 | 30 | def make_request(url, method, params=None, data=None, timeout=None, files=None): 31 | """Send a request and handle exceptions and logging.""" 32 | try: 33 | with requests.request( 34 | method, url, params=params, data=data, timeout=timeout, files=files 35 | ) as response: 36 | response.raise_for_status() 37 | return response.json() 38 | except (requests.RequestException, requests.exceptions.JSONDecodeError) as error: 39 | logging.error("Request to %s failed. Error: %s", url, error) 40 | return None 41 | 42 | 43 | def trello_request( 44 | _config, 45 | _settings, 46 | resource, 47 | method="GET", 48 | entity="boards", # Default to boards if not specified 49 | timeout=None, 50 | files=None, 51 | **kwargs, 52 | ): 53 | """Send a request to Trello API and return the response.""" 54 | # Filter out None values from kwargs 55 | kwargs = {k: v for k, v in kwargs.items() if v is not None} 56 | 57 | # Construct the URL based on the provided parameters 58 | url = construct_url(_settings["BASE_URL"], entity, resource, **kwargs) 59 | 60 | query = {"key": _config["API_KEY"], "token": _config["OAUTH_TOKEN"]} 61 | query.update(kwargs) # Always add the kwargs to the query parameters 62 | 63 | logging.info("Making a request to endpoint: %s with method: %s", method, url) 64 | return make_request(url, method, params=query, timeout=timeout, files=files) 65 | 66 | 67 | def construct_url(base_url, entity, resource, **kwargs): 68 | """ 69 | Construct the URL by joining base_url, entity, board_id (if provided), list_id (if provided), and resource. 70 | Ensure that there are no double slashes. 71 | """ 72 | # Prepare a list to hold all components of the URL. 73 | url_components = [base_url.rstrip("/")] # Ensure base_url doesn't end with a slash 74 | 75 | # Add the entity (boards, lists, cards, etc.) 76 | url_components.append(entity) 77 | 78 | # If card_id is provided, add it 79 | if "card_id" in kwargs and kwargs["card_id"]: 80 | url_components.append(kwargs["card_id"]) 81 | else: 82 | # If board_id is provided, add it 83 | if "board_id" in kwargs and kwargs["board_id"]: 84 | url_components.append(kwargs["board_id"]) 85 | 86 | # If list_id is provided, add it 87 | if "list_id" in kwargs and kwargs["list_id"]: 88 | url_components.append(kwargs["list_id"]) 89 | 90 | # Add the resource without any leading slash 91 | url_components.append(resource.lstrip("/")) 92 | 93 | # Debug logs to identify the issue 94 | logging.debug("URL Components before cleaning: %s", url_components) 95 | 96 | # Filter out None or empty components and join them with '/' 97 | cleaned_url = "/".join(filter(None, url_components)) 98 | 99 | logging.debug("Constructed URL: %s", cleaned_url) 100 | return cleaned_url 101 | 102 | 103 | def download_image(url, filepath="tmp_image.png"): 104 | """Download an image from a given URL and save it to a specified path.""" 105 | try: 106 | response = requests.get(url, timeout=10) # Added timeout of 10 seconds 107 | if response.status_code == 200: 108 | with open(filepath, "wb") as file: 109 | file.write(response.content) 110 | return filepath 111 | else: 112 | logging.error( 113 | "Failed to download image. HTTP status code: %s", response.status_code 114 | ) 115 | return None 116 | except requests.Timeout: 117 | logging.error("Request to %s timed out.", url) 118 | return None 119 | 120 | 121 | def fetch_cards_from_list(_config, _settings, list_id): 122 | """Fetch all cards from a given list.""" 123 | logging.debug("Fetching cards for list_id: %s", list_id) 124 | if not list_id: 125 | logging.error("list_id is not provided when trying to fetch cards from a list.") 126 | return None 127 | return trello_request(_config, _settings, "cards", entity="lists", list_id=list_id) 128 | -------------------------------------------------------------------------------- /src/modules/utilities.py: -------------------------------------------------------------------------------- 1 | """ 2 | LeetCode Trello Utilities Module. 3 | 4 | This module offers utility functions tailored for managing LeetCode problem cards on Trello. 5 | It facilitates operations such as processing single or multiple problem cards, determining due dates, 6 | parsing and managing dates, and more. 7 | 8 | Functions: 9 | - generate_leetcode_link(title): Generates a direct link to a LeetCode problem based on its title. 10 | - generate_all_due_dates(topics, current_date, problems_per_day): Generates due dates for every problem, taking into account weekdays. 11 | - get_list_name_and_due_date(due_date, current_date): Determines the appropriate list name and due date based on the current date. 12 | - is_due_this_week(due_date, current_date): Checks if a specified due date falls within the current week. 13 | - get_next_working_day(date): Returns the next working day after a given date, excluding weekends. 14 | - get_max_cards_for_week(_settings): Calculates the maximum number of cards required for the week. 15 | - determine_new_due_date_and_list(label_names, current_date): Determines the new due date and list for a card based on its labels. 16 | - parse_card_due_date(card_due): Parses the 'due' date of a card into a datetime object. 17 | - load_comment_from_md_file(md_file_path): Loads the content of a markdown file and returns it as a string. 18 | 19 | Dependencies: 20 | - logging: Used for logging information and error messages. 21 | - os: Provides a way of using operating system-dependent functionality. 22 | - datetime: Used for date operations and manipulations. 23 | 24 | Author: Alex McGonigle @grannyprogramming 25 | """ 26 | 27 | 28 | import logging 29 | import os 30 | from datetime import timedelta, datetime 31 | 32 | logging.basicConfig( 33 | level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" 34 | ) 35 | 36 | 37 | def generate_leetcode_link(title): 38 | """Generate a direct LeetCode problem link based on its title.""" 39 | return f"https://leetcode.com/problems/{title.lower().replace(' ', '-')}/" 40 | 41 | 42 | def generate_all_due_dates(topics, current_date, problems_per_day): 43 | """Generate due dates for every problem, considering weekdays.""" 44 | due_dates = [] 45 | total_problems = sum(len(problems) for problems in topics.values()) 46 | day = current_date 47 | 48 | while ( 49 | len(due_dates) < total_problems 50 | ): # While loop to ensure all problems get a due date 51 | if day.weekday() < 5: # 0-4 denotes Monday to Friday 52 | for _ in range( 53 | min(problems_per_day, total_problems - len(due_dates)) 54 | ): # Ensure we don't overshoot the total problems 55 | due_dates.append(day) 56 | day += timedelta(days=1) 57 | else: 58 | day += timedelta(days=1) # Increment the day even if it's a weekend 59 | 60 | return due_dates 61 | 62 | 63 | def get_list_name_and_due_date(due_date, current_date): 64 | """Determine the appropriate list name and due date based on the current date.""" 65 | list_name = ( 66 | "Do this week" if is_due_this_week(due_date, current_date) else "Backlog" 67 | ) 68 | return list_name, due_date 69 | 70 | 71 | def is_due_this_week(due_date, current_date): 72 | """Determine if a given due date falls within the current week.""" 73 | start_of_week = current_date - timedelta(days=current_date.weekday()) 74 | end_of_week = start_of_week + timedelta(days=4) 75 | return start_of_week <= due_date <= end_of_week 76 | 77 | 78 | def get_next_working_day(date): 79 | """Get the next working day after a given date, skipping weekends.""" 80 | next_day = date + timedelta(days=1) 81 | while next_day.weekday() >= 5: # Skip weekends 82 | next_day += timedelta(days=1) 83 | return next_day 84 | 85 | 86 | def get_max_cards_for_week(_settings): 87 | """Calculate maximum cards for the week.""" 88 | return _settings["PROBLEMS_PER_DAY"] * _settings["WORKDAYS"] 89 | 90 | 91 | def determine_new_due_date_and_list(label_names, current_date): 92 | """Determine the new due date and list based on labels.""" 93 | if "Do not know" in label_names: 94 | new_due_date = get_next_working_day(current_date) 95 | return new_due_date, "Do this week" 96 | elif "Somewhat know" in label_names: 97 | new_due_date = get_next_working_day(current_date + timedelta(weeks=1)) 98 | list_name = ( 99 | "Do this week" 100 | if is_due_this_week(new_due_date, current_date) 101 | else "Backlog" 102 | ) 103 | return new_due_date, list_name 104 | elif "Know" in label_names: 105 | new_due_date = get_next_working_day(current_date + timedelta(weeks=4)) 106 | return new_due_date, "Completed" 107 | return None, None 108 | 109 | 110 | def parse_card_due_date(card_due): 111 | """Parse the 'due' date of a card into a datetime object.""" 112 | return datetime.fromisoformat(card_due.replace("Z", "")) 113 | 114 | 115 | def load_comment_from_md_file(md_file_path): 116 | """ 117 | Load the content of the markdown file and return as a string. 118 | """ 119 | github_workspace = os.environ.get("GITHUB_WORKSPACE", "") 120 | absolute_path = os.path.join(github_workspace, md_file_path) 121 | 122 | with open(absolute_path, "r", encoding="utf-8") as md_file: 123 | return md_file.read() 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Trello LeetCoding75 Scheduler 💡 2 | 3 | Automate and optimize your LeetCode challenge preparation with the Trello LeetCoding75 Scheduler. This tool helps create a systematic study plan by categorizing challenges on a Trello board based on topic, difficulty, and due dates. 4 | 5 | ![Trello_Board](./imgs/trello_board.png) 6 | 7 | 8 | ## Features 🌟 9 | 10 | - **Trello Integration**: Direct interaction with the Trello API for seamless card, list, and label management. 11 | - **Topic-based Categorization**: Challenges are grouped under topics like Arrays, Two Pointers, Binary Trees, etc., for a comprehensive study approach. 12 | - **Automated Due Dates**: Assigns due dates to challenges, offering a structured study plan. 13 | - **Direct Challenge Links**: Each card contains a direct link to the respective LeetCode challenge for easy access. 14 | - **Customizability**: Modify the script to adapt to individual study plans or different challenge sets. 15 | 16 | ## Spaced Repetition Learning 🧠🔄 17 | 18 | One of the core principles that influenced the design of this scheduler is the concept of **Spaced Repetition**. It's a scientifically-backed learning technique where information is reviewed at increasing intervals, ensuring long-term retention and efficient learning. By spacing out your LeetCode challenges, you're not just practicing but also reinforcing your memory and understanding of algorithms and data structures. 19 | 20 | ### Why Spaced Repetition? 21 | 22 | - **Enhanced Retention**: It reduces the forgetting curve, ensuring that you remember algorithms and solutions for a longer duration. 23 | - **Efficient Learning**: Instead of cramming, spaced repetition promotes steady, incremental learning, which is proven to be more effective. 24 | - **Adaptive**: The technique adjusts to your learning pace. Topics you're familiar with will appear less often, while challenging ones will be reviewed more frequently. 25 | 26 | ### How It Works: 27 | 28 | 1. **Weekly Challenges**: 29 | - Each week, the scheduler ensures that the 'To Do this Week' list contains the required number of cards, as defined in the settings. 30 | - Some cards, based on specific labels, are exempt from this count. 31 | 32 | 2. **Retrospective Processing**: 33 | - Cards in the 'Retrospective' list are periodically processed. 34 | - Their due dates and positions are adjusted based on their associated labels: 35 | - **Do not know**: The challenge is scheduled for the next working day in the 'Do this week' list. 36 | - **Somewhat know**: The challenge is scheduled for a week later. Depending on the new due date, it might remain in the 'Do this week' list or be moved to the 'Backlog'. 37 | - **Know**: The challenge is deemed completed and is scheduled for a review four weeks later in the 'Completed' list. 38 | 39 | 3. **Backlog Management**: 40 | - If there's a shortfall in the 'To Do this Week' list, cards from the 'Backlog' are promoted to fill the gap. 41 | 42 | ## Retrospective Review 📝 43 | 44 | After tackling each LeetCode challenge during the week, setting aside time to introspect on your comprehension is pivotal. We've introduced a structured comment to aid this self-assessment, guiding you through: 45 | 46 | - **Problem Understanding**: Reevaluate your grasp of the challenge. 47 | - **Solution Strategy**: Deliberate on your chosen methodologies. 48 | - **Efficiency Analysis**: Consider the performance and robustness of your solution. 49 | - **Code Implementation**: Reflect on the coding phase, noting any challenges or revelations. 50 | - **Test Depth**: Gauge the comprehensiveness of your tests and consideration of edge cases. 51 | - **Learning Takeaways**: Identify any new algorithms or concepts you encountered. 52 | - **Solution Benchmarking**: Compare your method with alternatives in terms of simplicity and efficiency. 53 | - **Future Adaptations**: Think about potential variations of the problem and how you'd adapt your strategy. 54 | 55 | 56 | ### Review Process: 57 | 58 | 59 | 1. **Card Movement**: After your self-assessment, manually move the challenge card from the 'To Do this Week' list to the 'Retrospective' list on Trello. 60 | 2. **Label Assignment**: Within the 'Retrospective' list, apply one of the following labels to the card based on your understanding: 61 | - **Know**: You feel confident about your grasp and solution. 62 | - **Somewhat Know**: You understand the basics but had certain uncertainties. 63 | - **Do Not Know**: You found the challenge particularly tough and had difficulties. 64 | 65 | ### Spaced Repetition Integration: 66 | 67 | Once you've labeled your challenges in the 'Retrospective' list: 68 | 69 | 1. **Pipeline Execution**: 70 | - **Manual Trigger**: If you wish to see the changes reflected immediately, navigate to the `Actions` tab of your GitHub repository, select the Run Trello Script, and manually trigger the pipeline. (Will update this in the future to move automatically) 71 | - **Scheduled Run**: Alternatively, if you prefer to wait, the pipeline is set to run automatically at 01:00 UTC every Monday. 72 | 2. **Label-Based Scheduling**: 73 | - **Know**: Challenges labeled as "Know" are scheduled for infrequent reviews, ensuring you're revisiting them just enough to retain your understanding. 74 | - **Somewhat Know**: These challenges are scheduled for moderate frequency, allowing you to reinforce and solidify your understanding over time. 75 | - **Do Not Know**: Challenges with this label are set for more frequent reviews, ensuring you get ample practice until you're confident. 76 | 77 | 78 | ## Setup 🛠️ 79 | 80 | ### 1. Obtain API Key from Trello: 81 | 82 | - **Login**: Ensure you're logged into your [Trello account](https://trello.com/). 83 | - **Developer Portal**: Navigate to the [Power-Up Admin Portal](https://trello.com/power-ups/admin/). 84 | - **API Key**: View and manage your developer API keys. If you haven't created one, add a new one. Once created, copy and safely store your API key. 85 | 86 | ![API Key Creation](./imgs/api_key.png) 87 | 88 | ### 2. Obtain OAuth Token from Trello: 89 | 90 | - **Generate Token**: After obtaining your API key, https://trello.com/1/authorize?expiration=1day&name=MyPersonalToken&scope=read,write&response_type=token&key=YOUR_API_KEY. Replace `YOUR_API_KEY` with your Trello API key and navigate to this new URL. 91 | - **Authorize**: Grant the token access to your Trello account. 92 | - **Get Token**: Post authorization, you'll receive an OAuth token. Copy and store it securely. 93 | 94 | ### 3. Store Tokens as GitHub Secrets: 95 | 96 | - **Repository**: Navigate to your GitHub repository. 97 | - **Settings**: Access the Settings tab on your repository page. 98 | - **Secrets**: On the left sidebar, click on Secrets & Variables. 99 | - **Add Secrets**: Click on the `New repository secret` button. Add both `API_KEY` (from step 1) and `TRELLO_OAUTH_TOKEN` (from step 2). 100 | 101 | ## Usage 🚀 102 | 103 | This repository is set up with a GitHub Actions workflow. Once the setup is complete, the script (`leetcode_trello_manager.py`) will automatically run every Monday at 01:00 UTC, as defined in the workflow. 104 | 105 | ### Manual Trigger (Optional): 106 | 107 | If you wish to trigger the script manually: 108 | 109 | 1. Navigate to the `Actions` tab of your GitHub repository. 110 | 2. Select the `Run Trello Script` workflow from the left sidebar. 111 | 3. Click on `Run workflow` to manually trigger the script. 112 | 113 | Note: Ensure that the secrets (`API_KEY` and `TRELLO_OAUTH_TOKEN`) are set up correctly in the repository for the script to function as expected during the workflow run. 114 | 115 | ## Troubleshooting 🛠️🔍 116 | 117 | During the course of using this scheduler, you might encounter certain issues. Here are some common ones and their solutions: 118 | 119 | ### GitHub Action Failures: 120 | 121 | - **Error Logs**: Always check the error logs provided by GitHub Actions. They can provide insights into what went wrong. 122 | - **Secrets**: Ensure the `API_KEY` and `TRELLO_OAUTH_TOKEN` are correctly set in your repository secrets. 123 | - **Trello Permissions**: The provided API key and OAuth token must have the necessary permissions on Trello to create and manage boards, lists, and cards. 124 | 125 | ### Trello Authentication Issues: 126 | 127 | - **Token Expiry**: If you face authentication issues, it might be because the OAuth token has expired. Follow the steps in the setup section to generate a new token. 128 | - **API Key**: Ensure the API key hasn't been revoked or deleted from the Trello Developer Portal. 129 | 130 | If you face an issue that's not listed above, please raise an issue in the GitHub repository, and we'll address it at the earliest. 131 | 132 | ## Customization 🎨 133 | 134 | The Trello LeetCoding75 Scheduler is designed to be flexible and customizable according to your needs. Here's how you can adjust the settings to match your preferences: 135 | 136 | ### 1. Modifying the Problem Sets 🧩 137 | 138 | The problem sets are organized into various categories like "Arrays and Strings", "Two Pointers", "Sliding Window", and so on. Each problem has a title and an associated difficulty. 139 | 140 | To adjust the problems: 141 | - Open the provided JSON file. 142 | - Under the desired category, you can add, remove, or modify problems as you see fit. 143 | - Ensure each problem has a "title" and a "difficulty" key. 144 | 145 | ### 2. Adjusting Trello Settings ⚙️ 146 | 147 | The settings for interacting with Trello are contained within the `settings.ini` file. Here's how you can modify them: 148 | 149 | - **Week Settings**: The `[WEEK]` section contains the details about the workweek. Adjust `START_DAY`, `END_DAY`, and `WORKDAYS` according to your work schedule. 150 | - **Labels**: The `[LABELS]` section defines the default colors for various difficulty levels or problem statuses. Modify the `DEFAULT_COLORS` setting to add or change label colors. 151 | - **Problems Per Day**: Under the `[PROBLEMS]` section, you can set the `PROBLEMS_PER_DAY` to determine how many problems you want to tackle each day. 152 | 153 | ## Contribution 🤝 154 | 155 | Feel free to fork this repository, make changes, and open a pull request if you think you can make any improvements or have any suggestions. 156 | 157 | ## License ⚖️ 158 | 159 | [MIT License](LICENSE) 160 | -------------------------------------------------------------------------------- /config/leetcode75.json: -------------------------------------------------------------------------------- 1 | { 2 | "Arrays and Strings": [ 3 | { 4 | "title": "Merge Strings Alternately", 5 | "difficulty": "Easy" 6 | }, 7 | { 8 | "title": "Greatest Common Divisor of Strings", 9 | "difficulty": "Easy" 10 | }, 11 | { 12 | "title": "Kids With the Greatest Number of Candies", 13 | "difficulty": "Easy" 14 | }, 15 | { 16 | "title": "Can Place Flowers", 17 | "difficulty": "Easy" 18 | }, 19 | { 20 | "title": "Reverse Vowels of a String", 21 | "difficulty": "Easy" 22 | }, 23 | { 24 | "title": "Reverse Words in a String", 25 | "difficulty": "Medium" 26 | }, 27 | { 28 | "title": "Product of Array Except Self", 29 | "difficulty": "Medium" 30 | }, 31 | { 32 | "title": "Increasing Triplet Subsequence", 33 | "difficulty": "Medium" 34 | }, 35 | { 36 | "title": "String Compression", 37 | "difficulty": "Medium" 38 | } 39 | ], 40 | "Two Pointers": [ 41 | { 42 | "title": "Move Zeroes", 43 | "difficulty": "Easy" 44 | }, 45 | { 46 | "title": "Is Subsequence", 47 | "difficulty": "Easy" 48 | }, 49 | { 50 | "title": "Container With Most Water", 51 | "difficulty": "Medium" 52 | }, 53 | { 54 | "title": "Max Number of K-Sum Pairs", 55 | "difficulty": "Medium" 56 | } 57 | ], 58 | "Sliding Window": [ 59 | { 60 | "title": "Maximum Average Subarray I", 61 | "difficulty": "Easy" 62 | }, 63 | { 64 | "title": "Maximum Number of Vowels in a Substring of Given Length", 65 | "difficulty": "Medium" 66 | }, 67 | { 68 | "title": "Max Consecutive Ones III", 69 | "difficulty": "Medium" 70 | }, 71 | { 72 | "title": "Longest Subarray of 1's After Deleting One Element", 73 | "difficulty": "Medium" 74 | } 75 | ], 76 | "Prefix Sum": [ 77 | { 78 | "title": "Find the Highest Altitude", 79 | "difficulty": "Easy" 80 | }, 81 | { 82 | "title": "Find Pivot Index", 83 | "difficulty": "Easy" 84 | } 85 | ], 86 | "Hash Map and Set": [ 87 | { 88 | "title": "Find the Difference of Two Arrays", 89 | "difficulty": "Easy" 90 | }, 91 | { 92 | "title": "Unique Number of Occurrences", 93 | "difficulty": "Easy" 94 | }, 95 | { 96 | "title": "Determine if Two Strings Are Close", 97 | "difficulty": "Medium" 98 | }, 99 | { 100 | "title": "Equal Row and Column Pairs", 101 | "difficulty": "Medium" 102 | } 103 | ], 104 | "Stack": [ 105 | { 106 | "title": "Removing Stars From a String", 107 | "difficulty": "Medium" 108 | }, 109 | { 110 | "title": "Asteroid Collision", 111 | "difficulty": "Medium" 112 | }, 113 | { 114 | "title": "Decode String", 115 | "difficulty": "Medium" 116 | } 117 | ], 118 | "Queue": [ 119 | { 120 | "title": "Number of Recent Calls", 121 | "difficulty": "Easy" 122 | }, 123 | { 124 | "title": "Dota2 Senate", 125 | "difficulty": "Medium" 126 | } 127 | ], 128 | "Linked List": [ 129 | { 130 | "title": "Delete the Middle Node of a Linked List", 131 | "difficulty": "Medium" 132 | }, 133 | { 134 | "title": "Odd Even Linked List", 135 | "difficulty": "Medium" 136 | }, 137 | { 138 | "title": "Reverse Linked List", 139 | "difficulty": "Easy" 140 | }, 141 | { 142 | "title": "Maximum Twin Sum of a Linked List", 143 | "difficulty": "Medium" 144 | } 145 | ], 146 | "Binary Tree - DFS": [ 147 | { 148 | "title": "Maximum Depth of Binary Tree", 149 | "difficulty": "Easy" 150 | }, 151 | { 152 | "title": "Leaf-Similar Trees", 153 | "difficulty": "Easy" 154 | }, 155 | { 156 | "title": "Count Good Nodes in Binary Tree", 157 | "difficulty": "Medium" 158 | }, 159 | { 160 | "title": "Path Sum III", 161 | "difficulty": "Medium" 162 | }, 163 | { 164 | "title": "Longest ZigZag Path in a Binary Tree", 165 | "difficulty": "Medium" 166 | }, 167 | { 168 | "title": "Lowest Common Ancestor of a Binary Tree", 169 | "difficulty": "Medium" 170 | } 171 | ], 172 | "Binary Tree - BFS": [ 173 | { 174 | "title": "Binary Tree Right Side View", 175 | "difficulty": "Medium" 176 | }, 177 | { 178 | "title": "Maximum Level Sum of a Binary Tree", 179 | "difficulty": "Medium" 180 | } 181 | ], 182 | "Binary Search Tree": [ 183 | { 184 | "title": "Search in a Binary Search Tree", 185 | "difficulty": "Easy" 186 | }, 187 | { 188 | "title": "Delete Node in a BST", 189 | "difficulty": "Medium" 190 | } 191 | ], 192 | "Graphs - DFS": [ 193 | { 194 | "title": "Keys and Rooms", 195 | "difficulty": "Medium" 196 | }, 197 | { 198 | "title": "Number of Provinces", 199 | "difficulty": "Medium" 200 | }, 201 | { 202 | "title": "Reorder Routes to Make All Paths Lead to the City Zero", 203 | "difficulty": "Medium" 204 | }, 205 | { 206 | "title": "Evaluate Division", 207 | "difficulty": "Medium" 208 | } 209 | ], 210 | "Graphs - BFS": [ 211 | { 212 | "title": "Nearest Exit from Entrance in Maze", 213 | "difficulty": "Medium" 214 | }, 215 | { 216 | "title": "Rotting Oranges", 217 | "difficulty": "Medium" 218 | } 219 | ], 220 | "Heap and Priority Queue": [ 221 | { 222 | "title": "Kth Largest Element in an Array", 223 | "difficulty": "Medium" 224 | }, 225 | { 226 | "title": "Smallest Number in Infinite Set", 227 | "difficulty": "Medium" 228 | }, 229 | { 230 | "title": "Maximum Subsequence Score", 231 | "difficulty": "Medium" 232 | }, 233 | { 234 | "title": "Total Cost to Hire K Workers", 235 | "difficulty": "Medium" 236 | } 237 | ], 238 | "Binary Search": [ 239 | { 240 | "title": "Guess Number Higher or Lower", 241 | "difficulty": "Easy" 242 | }, 243 | { 244 | "title": "Successful Pairs of Spells and Potions", 245 | "difficulty": "Medium" 246 | }, 247 | { 248 | "title": "Find Peak Element", 249 | "difficulty": "Medium" 250 | }, 251 | { 252 | "title": "Koko Eating Bananas", 253 | "difficulty": "Medium" 254 | } 255 | ], 256 | "Backtracking": [ 257 | { 258 | "title": "Letter Combinations of a Phone Number", 259 | "difficulty": "Medium" 260 | }, 261 | { 262 | "title": "Combination Sum III", 263 | "difficulty": "Medium" 264 | } 265 | ], 266 | "DP - 1D": [ 267 | { 268 | "title": "N-th Tribonacci Number", 269 | "difficulty": "Easy" 270 | }, 271 | { 272 | "title": "Min Cost Climbing Stairs", 273 | "difficulty": "Easy" 274 | }, 275 | { 276 | "title": "House Robber", 277 | "difficulty": "Medium" 278 | }, 279 | { 280 | "title": "Domino and Tromino Tiling", 281 | "difficulty": "Medium" 282 | } 283 | ], 284 | "DP - Multidimensional": [ 285 | { 286 | "title": "Unique Paths", 287 | "difficulty": "Medium" 288 | }, 289 | { 290 | "title": "Longest Common Subsequence", 291 | "difficulty": "Medium" 292 | }, 293 | { 294 | "title": "Best Time to Buy and Sell Stock with Transaction Fee", 295 | "difficulty": "Medium" 296 | }, 297 | { 298 | "title": "Edit Distance", 299 | "difficulty": "Medium" 300 | } 301 | ], 302 | "Bit Manipulation": [ 303 | { 304 | "title": "Counting Bits", 305 | "difficulty": "Easy" 306 | }, 307 | { 308 | "title": "Single Number", 309 | "difficulty": "Easy" 310 | }, 311 | { 312 | "title": "Minimum Flips to Make a OR b Equal to c", 313 | "difficulty": "Medium" 314 | } 315 | ], 316 | "Trie": [ 317 | { 318 | "title": "Implement Trie (Prefix Tree)", 319 | "difficulty": "Medium" 320 | }, 321 | { 322 | "title": "Search Suggestions System", 323 | "difficulty": "Medium" 324 | } 325 | ], 326 | "Intervals": [ 327 | { 328 | "title": "Non-overlapping Intervals", 329 | "difficulty": "Medium" 330 | }, 331 | { 332 | "title": "Minimum Number of Arrows to Burst Balloons", 333 | "difficulty": "Medium" 334 | } 335 | ], 336 | "Monotonic Stack": [ 337 | { 338 | "title": "Daily Temperatures", 339 | "difficulty": "Medium" 340 | }, 341 | { 342 | "title": "Online Stock Span", 343 | "difficulty": "Medium" 344 | } 345 | ] 346 | } 347 | -------------------------------------------------------------------------------- /src/modules/board_operations.py: -------------------------------------------------------------------------------- 1 | """ 2 | Trello Board Management Module. 3 | 4 | This module provides utility functions for managing various aspects of Trello boards. 5 | It supports operations such as fetching and setting board background images, managing board lists, 6 | creating and deleting labels on boards, creating boards based on names, and more. 7 | 8 | Functions: 9 | - fetch_image(): Downloads the background image from a specified URL. 10 | - set_board_background_image(board_id): Sets a custom background image for a specified Trello board. 11 | - manage_board_lists(board_id): Manages the default and required lists on a Trello board. 12 | - create_missing_labels(board_id): Creates any missing labels on a specified Trello board based on predefined defaults. 13 | - fetch_all_list_ids(_config, _settings, board_id): Retrieves all list IDs for a given board. 14 | - fetch_all_label_ids(_config, _settings, board_id): Retrieves all label IDs for a given board. 15 | - create_board(_config, _settings, board_name): Creates a new Trello board, deletes default lists and labels, and returns its ID. 16 | - get_board_id(_config, _settings, board_name): Gets the board ID given a board name or creates it if it doesn't exist. 17 | - delete_list(_config, _settings, board_id, list_name): Deletes a list on a board. 18 | - check_list_exists(_config, _settings, board_id, list_name): Checks if a list exists on a board. 19 | - create_list(_config, _settings, board_id, list_name): Creates a new list on a board. 20 | - upload_custom_board_background(_config, _settings, member_id, image_filepath): Uploads a custom background image for the board. 21 | - set_custom_board_background(_config, _settings, board_id, background_id): Sets a custom background for the board. 22 | - get_member_id(_config, _settings): Retrieves the member ID. 23 | - get_labels_on_board(_config, _settings, board_id): Fetches all labels on a board. 24 | - delete_label(_config, _settings, label_id): Deletes a specific label. 25 | - delete_all_labels(_config, _settings, board_id): Deletes all labels on a board. 26 | 27 | Dependencies: 28 | - os: Provides a way of using operating system-dependent functionality. 29 | - logging: Used for logging information and error messages. 30 | - .trello_api: Houses Trello-specific API functions. 31 | - ._config_loader: Provides functions to load configurations and settings. 32 | 33 | Globals: 34 | - _settings: Global variable storing loaded settings from an INI file. 35 | - _config: Global variable storing loaded configurations. 36 | - TRELLO_ENTITY: Dictionary containing constants for different Trello entities. 37 | 38 | Author: Alex McGonigle @grannyprogramming 39 | """ 40 | 41 | import os 42 | import logging 43 | from .trello_api import download_image, trello_request 44 | from .config_loader import load_ini_settings, load_config 45 | 46 | logging.basicConfig( 47 | level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" 48 | ) 49 | 50 | _settings = load_ini_settings() 51 | _config = load_config() 52 | 53 | # Constants 54 | TRELLO_ENTITY = {"BOARD": "boards", "MEMBER": "members", "LIST": "lists"} 55 | 56 | 57 | def fetch_image(): 58 | """Fetches the background image from a given URL.""" 59 | return download_image(f"{_config['RAW_URL_BASE']}imgs/background/groot.png") 60 | 61 | 62 | def set_board_background_image(board_id): 63 | """Sets the board background image.""" 64 | member_id = get_member_id(_config, _settings) 65 | if not member_id: 66 | raise ValueError("Failed to retrieve member ID") 67 | 68 | image_filepath = fetch_image() 69 | if not image_filepath: 70 | raise ValueError("Failed to download image") 71 | 72 | background_id = upload_custom_board_background( 73 | _config, _settings, member_id, image_filepath 74 | ) 75 | if not background_id: 76 | raise ValueError("Failed to upload custom board background image") 77 | 78 | if not set_custom_board_background(_config, _settings, board_id, background_id): 79 | raise ValueError("Failed to set board background image") 80 | 81 | 82 | def manage_board_lists(board_id): 83 | """Manages the required lists for a given board.""" 84 | 85 | for required_list in _settings["REQUIRED_LISTS"]: 86 | if not check_list_exists(_config, _settings, board_id, required_list): 87 | create_list(_config, _settings, board_id, required_list) 88 | 89 | 90 | def create_missing_labels(board_id): 91 | """Creates missing labels for a given board.""" 92 | labels = trello_request(_config, _settings, f"{board_id}/labels", entity="boards") 93 | if labels is None: 94 | raise ValueError(f"Failed to fetch labels for board with ID: {board_id}") 95 | 96 | label_names = [l.get("name") for l in labels if "name" in l] 97 | for label, color in _settings["DEFAULT_LABELS_COLORS"].items(): 98 | if label not in label_names: 99 | trello_request( 100 | _config, 101 | _settings, 102 | "labels", 103 | "POST", 104 | entity="boards", 105 | board_id=board_id, 106 | name=label, 107 | color=color, 108 | ) 109 | logging.info( 110 | "Created label %s with color %s for board ID: %s", 111 | label, 112 | color, 113 | board_id, 114 | ) 115 | 116 | 117 | def fetch_all_list_ids(_config, _settings, board_id): 118 | """Retrieve all list IDs for a given board.""" 119 | response = trello_request(_config, _settings, f"{board_id}/lists") 120 | if response is None: 121 | logging.error("Failed to fetch lists for board with ID: %s", board_id) 122 | return {} 123 | list_ids = {l["name"]: l["id"] for l in response} 124 | logging.debug("Fetched list IDs: %s", list_ids) 125 | return list_ids 126 | 127 | 128 | def fetch_all_label_ids(_config, _settings, board_id): 129 | """Retrieve all label IDs for a given board.""" 130 | response = trello_request( 131 | _config, _settings, "labels", entity="boards", board_id=board_id 132 | ) 133 | if response is None: 134 | logging.error("Failed to fetch labels for board with ID: %s", board_id) 135 | return {} 136 | return {l["name"]: l["id"] for l in response} 137 | 138 | 139 | def create_board(_config, _settings, board_name): 140 | """Create a new Trello board, delete default lists, delete all labels, and return its ID.""" 141 | new_board = trello_request( 142 | _config, _settings, resource="", method="POST", name=board_name 143 | ) 144 | 145 | # Log the response for debugging 146 | logging.info("Response from board creation: %s", new_board) 147 | 148 | if new_board and "id" in new_board: 149 | logging.info("Successfully created board with ID: %s", new_board["id"]) 150 | 151 | # Delete default lists for the newly created board 152 | for default_list in _settings["DEFAULT_LISTS"]: 153 | if check_list_exists(_config, _settings, new_board["id"], default_list): 154 | delete_list(_config, _settings, new_board["id"], default_list) 155 | 156 | # Delete all labels for the newly created board 157 | delete_all_labels(_config, _settings, new_board["id"]) 158 | 159 | return new_board["id"] 160 | else: 161 | logging.error("Failed to create board with name: %s", board_name) 162 | return None 163 | 164 | 165 | def get_board_id(_config, _settings, board_name): 166 | """Get the board ID given a board name. If the board does not exist or is closed, create it.""" 167 | boards = trello_request(_config, _settings, resource="me/boards", entity="members") 168 | 169 | # Check if an open board with the given name exists 170 | board_id = next( 171 | ( 172 | board["id"] 173 | for board in boards 174 | if board["name"] == board_name and not board["closed"] 175 | ), 176 | None, 177 | ) 178 | 179 | # If board doesn't exist or is closed, create it 180 | if not board_id: 181 | board_id = create_board(_config, _settings, board_name) 182 | logging.info("Created a new board with ID: %s", board_id) 183 | else: 184 | logging.info("Using existing board with ID: %s", board_id) 185 | 186 | if not board_id: 187 | logging.error("Failed to find or create a board with name: %s", board_name) 188 | 189 | return board_id 190 | 191 | 192 | def delete_list(_config, _settings, board_id, list_name): 193 | """Delete a list on the board.""" 194 | lists = trello_request(_config, _settings, f"{board_id}/lists") 195 | list_id = next(lst["id"] for lst in lists if lst["name"] == list_name) 196 | return trello_request( 197 | _config, 198 | _settings, 199 | f"{list_id}/closed", 200 | method="PUT", 201 | entity=TRELLO_ENTITY["LIST"], 202 | value="true", 203 | ) 204 | 205 | 206 | def check_list_exists(_config, _settings, board_id, list_name): 207 | """Check if a list exists on the board.""" 208 | lists = trello_request(_config, _settings, f"{board_id}/lists") 209 | return any(lst["name"] == list_name for lst in lists) 210 | 211 | 212 | def create_list(_config, _settings, board_id, list_name): 213 | """Create a new list on a board.""" 214 | return trello_request( 215 | _config, 216 | _settings, 217 | "", 218 | method="POST", 219 | entity=TRELLO_ENTITY["LIST"], 220 | idBoard=board_id, 221 | name=list_name, 222 | ) 223 | 224 | 225 | def upload_custom_board_background(_config, _settings, member_id, image_filepath): 226 | """Upload a custom background image for the board.""" 227 | endpoint = f"members/{member_id}/customBoardBackgrounds" 228 | with open(image_filepath, "rb") as file: 229 | files = {"file": (os.path.basename(image_filepath), file, "image/png")} 230 | response = trello_request( 231 | _config, _settings, endpoint, method="POST", entity="", files=files 232 | ) 233 | return response.get("id") if response else None 234 | 235 | 236 | def set_custom_board_background(_config, _settings, board_id, background_id): 237 | """Set a custom background for the board.""" 238 | endpoint = f"{board_id}/prefs/background" 239 | response = trello_request( 240 | _config, 241 | _settings, 242 | endpoint, 243 | method="PUT", 244 | entity=TRELLO_ENTITY["BOARD"], 245 | value=background_id, 246 | ) 247 | return response if response else None 248 | 249 | 250 | def get_member_id(_config, _settings): 251 | """Retrieve the member ID.""" 252 | response = trello_request(_config, _settings, "me", entity=TRELLO_ENTITY["MEMBER"]) 253 | return response.get("id") if response else None 254 | 255 | 256 | def get_labels_on_board(_config, _settings, board_id): 257 | """Fetch all labels on the board.""" 258 | return trello_request(_config, _settings, f"{board_id}/labels") 259 | 260 | 261 | def delete_label(_config, _settings, label_id): 262 | """Delete a label.""" 263 | return trello_request( 264 | _config, _settings, label_id, method="DELETE", entity="labels" 265 | ) 266 | 267 | 268 | def delete_all_labels(_config, _settings, board_id): 269 | """Delete all labels on the board.""" 270 | labels = get_labels_on_board(_config, _settings, board_id) 271 | for label in labels: 272 | delete_label(_config, _settings, label["id"]) 273 | -------------------------------------------------------------------------------- /src/modules/card_operations.py: -------------------------------------------------------------------------------- 1 | """ 2 | Trello Card Management Module. 3 | 4 | This module provides utility functions for managing various aspects of Trello cards. 5 | It supports operations such as checking if a card exists, filtering cards based on labels, 6 | applying changes to cards, moving cards between lists, processing retrospective and completed cards, 7 | and more. 8 | 9 | Functions: 10 | - card_exists(_config, _settings, board_id, card_name): Checks if a card exists on a specified board. 11 | - filter_cards_by_label(cards, _settings): Filters out cards with specific labels defined in _settings. 12 | - apply_changes_to_cards(_config, _settings, list_ids, cards_to_add): Applies changes to Trello cards, 13 | especially for managing the "Do this week" list. 14 | - get_top_card_from_backlog(_config, _settings, list_ids): Retrieves the top card from the 'Backlog' list. 15 | - move_card_to_list(_config, _settings, card_id, target_list_id): Moves a card to a specified list. 16 | - process_retrospective_cards(_config, _settings, board_id, current_date): Processes the retrospective 17 | cards based on their labels and due dates. 18 | - process_completed_cards(_config, _settings, board_id, current_date): Moves completed cards that are due 19 | this week to the 'Do this week' list. 20 | - attach_image_to_card(_config, _settings, card_id, topic): Attaches an image to a specified card. 21 | - create_topic_label(_config, _settings, board_id, category): Creates a label for a given topic. 22 | - retest_cards(_config, _settings, board_name, current_date): Processes retest cards for a specified board. 23 | - manage_this_week_list(__config, __settings, board_id): Ensures the 'To Do this Week' list has the 24 | required number of cards. 25 | - get_max_cards_for_week(_settings): Calculates the maximum number of cards required for the week. 26 | - fetch_cards_from_list(_config, _settings, list_id): Fetches all cards from a given list. 27 | - process_single_problem_card(_config, _settings, board_id, list_ids, label_ids, topic_label_id, category, problem, due_date, current_date): Creates a Trello card for a single LeetCode problem. 28 | - process_all_problem_cards(_config, _settings, board_id, topics, current_date): Processes all problem cards for a given board. 29 | - add_comment_to_card(_config, _settings, card_id, comment_content): Adds a comment to a given card. 30 | 31 | Dependencies: 32 | - logging: Used for logging information and error messages. 33 | - .trello_api: Houses Trello-specific API functions. 34 | - .board_operations: Provides functions for board-related operations. 35 | - .utilities: Contains utility functions related to date parsing and filtering. 36 | 37 | Author: Alex McGonigle @grannyprogramming 38 | """ 39 | 40 | 41 | import logging 42 | from .trello_api import trello_request 43 | from .board_operations import fetch_all_list_ids, get_board_id, fetch_all_label_ids 44 | from .utilities import ( 45 | determine_new_due_date_and_list, 46 | parse_card_due_date, 47 | is_due_this_week, 48 | generate_leetcode_link, 49 | generate_all_due_dates, 50 | get_list_name_and_due_date, 51 | load_comment_from_md_file, 52 | ) 53 | 54 | logging.basicConfig( 55 | level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" 56 | ) 57 | 58 | 59 | def card_exists(_config, _settings, board_id, card_name): 60 | """Check if a card exists on the board.""" 61 | cards = trello_request(_config, _settings, f"{board_id}/cards") 62 | return any(card["name"] == card_name for card in cards) 63 | 64 | 65 | def filter_cards_by_label(cards, _settings): 66 | """Filter out cards with specific labels.""" 67 | if not cards: 68 | return [] 69 | 70 | # Extracting the label names from the _settings without considering the colors 71 | exclude_labels = set( 72 | label_name 73 | for label_name, _ in _settings["DEFAULT_LABELS_COLORS"].items() 74 | if label_name in ["Somewhat know", "Do not know", "Know"] 75 | ) 76 | 77 | # Filter out cards that have any of the excluded labels 78 | return [ 79 | card 80 | for card in cards 81 | if not exclude_labels & {label["name"] for label in card["labels"]} 82 | ] 83 | 84 | 85 | def apply_changes_to_cards(_config, _settings, list_ids, cards_to_add): 86 | """Apply the necessary changes to the Trello cards (like pulling cards from backlog).""" 87 | to_do_this_week_id = list_ids.get("Do this week") 88 | if not to_do_this_week_id: 89 | logging.error("To Do this Week ID not found when trying to apply changes.") 90 | return 91 | 92 | # Fetch the current cards in the "Do this week" list. 93 | current_cards = fetch_cards_from_list(_config, _settings, to_do_this_week_id) 94 | if not current_cards: 95 | current_cards = [] 96 | 97 | # Filter out the cards that have the labels "Somewhat know:blue", "Do not know:red", and "Know:green". 98 | filtered_cards = filter_cards_by_label(current_cards, _settings) 99 | 100 | # Calculate how many more cards are needed in the "Do this week" list to meet the weekly quota. 101 | cards_needed = get_max_cards_for_week(_settings) - len(filtered_cards) 102 | cards_to_pull = min(cards_needed, cards_to_add) 103 | 104 | for _ in range(cards_to_pull): 105 | top_card = get_top_card_from_backlog(_config, _settings, list_ids) 106 | if top_card: 107 | move_card_to_list(_config, _settings, top_card["id"], to_do_this_week_id) 108 | else: 109 | logging.warning("No more cards to pull from the 'Backlog'.") 110 | break 111 | 112 | 113 | def get_top_card_from_backlog(_config, _settings, list_ids): 114 | """ 115 | Get the top card from the 'Backlog' list. 116 | """ 117 | backlog_id = list_ids.get("Backlog") 118 | if not backlog_id: 119 | logging.error("Backlog ID not found when trying to get the top card.") 120 | return None 121 | backlog_cards = fetch_cards_from_list(_config, _settings, backlog_id) 122 | if not backlog_cards: 123 | logging.warning("No cards found in the 'Backlog' list.") 124 | return None 125 | return backlog_cards[0] 126 | 127 | 128 | def move_card_to_list(_config, _settings, card_id, target_list_id): 129 | """ 130 | Move a card to a specified list. 131 | """ 132 | trello_request( 133 | _config, 134 | _settings, 135 | card_id, # Just use the card_id as the resource. 136 | "PUT", 137 | entity="cards", # Explicitly mention the entity here. 138 | idList=target_list_id, 139 | ) 140 | logging.info("Moved card with ID %s to list with ID %s.", card_id, target_list_id) 141 | 142 | 143 | def process_retrospective_cards(_config, _settings, board_id, current_date): 144 | """Process the retrospective cards.""" 145 | list_ids = fetch_all_list_ids(_config, _settings, board_id) 146 | retrospective_list_name = _settings["REQUIRED_LISTS"][1] # "Retrospective" 147 | retrospective_cards = trello_request( 148 | _config, 149 | _settings, 150 | "cards", 151 | entity="lists", 152 | list_id=list_ids[retrospective_list_name], 153 | ) 154 | 155 | if retrospective_cards: 156 | for card in retrospective_cards: 157 | label_names = [label["name"] for label in card["labels"]] 158 | new_due_date, list_name = determine_new_due_date_and_list( 159 | label_names, current_date 160 | ) 161 | if not list_name: 162 | continue 163 | 164 | # Update the card's list and due date 165 | trello_request( 166 | _config, 167 | _settings, 168 | card["id"], 169 | "PUT", 170 | entity="cards", 171 | idList=list_ids[list_name], 172 | due=new_due_date.isoformat(), 173 | ) 174 | 175 | 176 | def process_completed_cards(_config, _settings, board_id, current_date): 177 | """Move completed cards that are due this week to the 'Do this week' list.""" 178 | list_ids = fetch_all_list_ids(_config, _settings, board_id) 179 | completed_list_name = _settings["REQUIRED_LISTS"][ 180 | 0 181 | ] # Assuming "Completed" is the first item in REQUIRED_LISTS 182 | completed_cards = trello_request( 183 | _config, 184 | _settings, 185 | "cards", 186 | entity="lists", 187 | list_id=list_ids[completed_list_name], 188 | ) 189 | 190 | for card in completed_cards: 191 | if is_due_this_week(parse_card_due_date(card["due"]), current_date): 192 | trello_request( 193 | _config, 194 | _settings, 195 | f"/cards/{card['id']}", 196 | "PUT", 197 | idList=list_ids["Do this week"], 198 | ) 199 | 200 | 201 | def attach_image_to_card(_config, _settings, card_id, topic): 202 | """Attach an image to a given card.""" 203 | image_url = f"{_config['RAW_URL_BASE']}imgs/cards/{topic}.png" 204 | response = trello_request( 205 | _config, 206 | _settings, 207 | f"{card_id}/attachments", 208 | "POST", 209 | entity="cards", 210 | url=image_url, 211 | ) 212 | if not response: 213 | logging.error("Failed to attach image to card %s", card_id) 214 | 215 | 216 | def create_topic_label(_config, _settings, board_id, category): 217 | """Create a label for a given topic.""" 218 | return trello_request( 219 | _config, 220 | _settings, 221 | "/labels", 222 | "POST", 223 | entity="boards", 224 | board_id=board_id, 225 | name=category, 226 | color="black", 227 | ) 228 | 229 | 230 | def retest_cards(_config, _settings, board_name, current_date): 231 | """Process retest cards for a given board.""" 232 | board_id = get_board_id(_config, _settings, board_name) 233 | process_retrospective_cards(_config, _settings, board_id, current_date) 234 | process_completed_cards(_config, _settings, board_id, current_date) 235 | logging.info("Retest cards processed!") 236 | 237 | 238 | def manage_this_week_list(__config, __settings, board_id): 239 | """ 240 | Ensure the 'To Do this Week' list has the required number of cards based on the _settings. 241 | Cards with specific labels are excluded from this count. 242 | """ 243 | max_cards = get_max_cards_for_week(__settings) 244 | 245 | # Fetch list IDs and get the ID for "To Do this Week" 246 | list_ids = fetch_all_list_ids(__config, __settings, board_id) 247 | to_do_this_week_name = __settings["REQUIRED_LISTS"][2] 248 | to_do_this_week_id = list_ids.get(to_do_this_week_name) 249 | 250 | # Fetch and filter cards 251 | cards = fetch_cards_from_list(__config, __settings, to_do_this_week_id) 252 | filtered_cards = filter_cards_by_label(cards, __settings) 253 | 254 | logging.info("Max cards for the week: %s", max_cards) 255 | logging.info( 256 | "Number of filtered cards in 'To Do this Week' list: %s", len(filtered_cards) 257 | ) 258 | 259 | # Calculate the number of cards to pull 260 | cards_to_pull_count = max_cards - len(filtered_cards) 261 | 262 | logging.info("Need to pull %s cards to meet the weekly quota.", cards_to_pull_count) 263 | 264 | apply_changes_to_cards(__config, __settings, list_ids, cards_to_pull_count) 265 | 266 | 267 | def get_max_cards_for_week(_settings): 268 | """Calculate maximum cards for the week.""" 269 | return _settings["PROBLEMS_PER_DAY"] * _settings["WORKDAYS"] 270 | 271 | 272 | def fetch_cards_from_list(_config, _settings, list_id): 273 | """Fetch all cards from a given list.""" 274 | logging.debug("Fetching cards for list_id: %s", list_id) 275 | if not list_id: 276 | logging.error("list_id is not provided when trying to fetch cards from a list.") 277 | return None 278 | return trello_request(_config, _settings, "cards", entity="lists", list_id=list_id) 279 | 280 | 281 | def add_comment_to_card(_config, _settings, card_id, comment_content): 282 | """Add a comment to a given card.""" 283 | response = trello_request( 284 | _config, 285 | _settings, 286 | f"{card_id}/actions/comments", 287 | "POST", 288 | entity="cards", 289 | text=comment_content, 290 | ) 291 | if not response: 292 | logging.error("Failed to add comment to card %s", card_id) 293 | 294 | 295 | def process_single_problem_card( 296 | _config, 297 | _settings, 298 | board_id, 299 | list_ids, 300 | label_ids, 301 | topic_label_id, 302 | category, 303 | problem, 304 | due_date, 305 | current_date, 306 | ): 307 | """ 308 | Create a Trello card for a single LeetCode problem. 309 | """ 310 | card_name = f"{category}: {problem['title']}" 311 | if not card_exists(_config, _settings, board_id, card_name): 312 | difficulty_label_id = label_ids.get(problem["difficulty"]) 313 | if not difficulty_label_id: 314 | logging.error( 315 | "Difficulty label not found for problem: %s", problem["title"] 316 | ) 317 | return 318 | link = generate_leetcode_link(problem["title"]) 319 | list_name, due_date_for_card = get_list_name_and_due_date( 320 | due_date, current_date 321 | ) 322 | card_response = trello_request( 323 | _config, 324 | _settings, 325 | resource="/cards", 326 | method="POST", 327 | entity="", 328 | idList=list_ids.get(list_name), 329 | name=card_name, 330 | desc=link, 331 | idLabels=[difficulty_label_id, topic_label_id], 332 | due=due_date_for_card.isoformat(), 333 | ) 334 | if not card_response: 335 | logging.error("Failed to create card: %s", card_name) 336 | return 337 | attach_image_to_card(_config, _settings, card_response["id"], category) 338 | comment_md_content = load_comment_from_md_file(_settings["COMMENT_MD_PATH"]) 339 | add_comment_to_card(_config, _settings, card_response["id"], comment_md_content) 340 | 341 | 342 | def process_all_problem_cards(_config, _settings, board_id, topics, current_date): 343 | """Process all problem cards for a given board.""" 344 | list_ids = fetch_all_list_ids(_config, _settings, board_id) 345 | label_ids = fetch_all_label_ids(_config, _settings, board_id) 346 | all_due_dates = generate_all_due_dates( 347 | topics, current_date, _settings["PROBLEMS_PER_DAY"] 348 | ) 349 | due_date_index = 0 350 | 351 | for category, problems in topics.items(): 352 | topic_label_response = create_topic_label( 353 | _config, _settings, board_id, category 354 | ) 355 | if topic_label_response is None: 356 | logging.error("Failed to create label for category: %s", category) 357 | continue 358 | topic_label_id = topic_label_response["id"] 359 | for problem in problems: 360 | process_single_problem_card( 361 | _config, 362 | _settings, 363 | board_id, 364 | list_ids, 365 | label_ids, 366 | topic_label_id, 367 | category, 368 | problem, 369 | all_due_dates[due_date_index], 370 | current_date, 371 | ) 372 | due_date_index += 1 373 | --------------------------------------------------------------------------------