From 2b49a609c222657321c2027b0790e89b63b01dbd Mon Sep 17 00:00:00 2001 From: brucewzj99 Date: Sat, 27 Jan 2024 12:06:48 +0800 Subject: [PATCH 1/3] init not tested, just give structure --- bot/google_sheet_service/__init__.py | 0 bot/google_sheet_service/auth.py | 13 +++ bot/google_sheet_service/config.py | 123 +++++++++++++++++++++++++ bot/google_sheet_service/exceptions.py | 0 bot/google_sheet_service/sheets_api.py | 9 ++ 5 files changed, 145 insertions(+) create mode 100644 bot/google_sheet_service/__init__.py create mode 100644 bot/google_sheet_service/auth.py create mode 100644 bot/google_sheet_service/config.py create mode 100644 bot/google_sheet_service/exceptions.py create mode 100644 bot/google_sheet_service/sheets_api.py diff --git a/bot/google_sheet_service/__init__.py b/bot/google_sheet_service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bot/google_sheet_service/auth.py b/bot/google_sheet_service/auth.py new file mode 100644 index 0000000..610ae12 --- /dev/null +++ b/bot/google_sheet_service/auth.py @@ -0,0 +1,13 @@ +import os +import json +from google.oauth2 import service_account + + +def get_credentials(): + SCOPES = ["https://www.googleapis.com/auth/spreadsheets"] + google_json = os.getenv("GOOGLE_JSON") + google_service = json.loads(google_json) + creds = service_account.Credentials.from_service_account_info( + google_service, scopes=SCOPES + ) + return creds diff --git a/bot/google_sheet_service/config.py b/bot/google_sheet_service/config.py new file mode 100644 index 0000000..41e5001 --- /dev/null +++ b/bot/google_sheet_service/config.py @@ -0,0 +1,123 @@ +# Sheet names +DD_SHEET = "Dropdown" # DD for Dropdown +TR_SHEET = "Tracker" # TR for Tracker + +# Tracker columns +TR_QUICKADD_TP_PAY = "G" # Transport Payment Column +TR_QUICKADD_TP_TYPE = "H" # Transport Type Column +TR_QUICKADD_OT_PAY = "I" # Others Payment Column +TR_QUICKADD_OT_TYPE = "J" # Others Type Column + +TR_QUICKADD_ROW_START = 3 # Quick Add Row +TR_QUICKADD_ROW_END = 13 # Quick Add End Row + +TR_START_COL = "B" # Start Column +TR_END_COL = "E" # End Column +TR_ROW = 3 + +# Dropdown rows +DD_MAIN_CAT_ROW = 2 +DD_SUBCAT_START = 3 +DD_SUBCAT_END = 9 + +DD_MAIN_PAY_ROW = 12 +DD_SUBPAY_START = 13 +DD_SUBPAY_END = 19 + +DD_TRANSPORT_COL = "A" +DD_OTHERS_COL_START = "B" +DD_OTHERS_COL_END = "J" + +DD_INCOME_COL = "L" + +DD_PAYMENT_COL_START = "A" +DD_PAYMENT_COL_END = "J" + +# Column indexes +START_COL_IDX = 0 +END_COL_IDX = 11 + +# Months +OVERALL_RANGE = "!M13:O25" + + +# Helper functions +def create_range(sheet, start_col, start_row, end_col=None, end_row=None): + """ + Create a standard range string. + Sample output: "Dropdown!A2:A9" + """ + end_part = f":{end_col}{end_row}" if end_col and end_row else "" + return f"{sheet}!{start_col}{start_row}{end_part}" + + +def create_complex_range(sheet, start_col_ord, end_col_ord, row_start, row_end): + """ + Create a range string for complex cases. + Sample output: ["Dropdown!B2:B9", "Dropdown!C2:C9", ...] + """ + return [ + f"{sheet}!{chr(i)}{row_start}:{chr(i)}{row_end}" + for i in range(start_col_ord, end_col_ord) + ] + + +# Transport ranges +TRANSPORT_RANGE = create_range( + DD_SHEET, DD_TRANSPORT_COL, DD_SUBCAT_START, DD_TRANSPORT_COL, DD_SUBCAT_END +) + +# Others ranges +OTHERS_MAIN_RANGE = create_range( + DD_SHEET, DD_OTHERS_COL_START, DD_MAIN_CAT_ROW, DD_OTHERS_COL_END, DD_MAIN_CAT_ROW +) +OTHERS_SUB_RANGE = create_complex_range( + DD_SHEET, + ord(DD_OTHERS_COL_START), + ord(DD_OTHERS_COL_END), + DD_MAIN_CAT_ROW, + DD_SUBCAT_END, +) + +# Payment ranges +PAYMENT_MAIN_RANGE = create_range( + DD_SHEET, DD_PAYMENT_COL_START, DD_MAIN_PAY_ROW, DD_PAYMENT_COL_END, DD_MAIN_PAY_ROW +) +PAYMENT_SUB_RANGE = create_complex_range( + DD_SHEET, + ord(DD_PAYMENT_COL_START), + ord(DD_PAYMENT_COL_END), + DD_MAIN_PAY_ROW, + DD_SUBPAY_END, +) + +# Income range +INCOME_RANGE = create_range( + DD_SHEET, DD_INCOME_COL, DD_MAIN_CAT_ROW, DD_INCOME_COL, DD_SUBCAT_END +) + +# Tracker ranges +TRACKER_RANGE = create_range(TR_SHEET, TR_START_COL, TR_ROW, TR_END_COL, TR_ROW) + +# Quick add ranges +QUICK_ADD_RANGE = create_range( + TR_SHEET, + TR_QUICKADD_TP_PAY, + TR_QUICKADD_ROW_START, + TR_QUICKADD_OT_TYPE, + TR_QUICKADD_ROW_START, +) +QUICK_OTHERS_RANGE = create_range( + TR_SHEET, + TR_QUICKADD_OT_PAY, + TR_QUICKADD_ROW_START, + TR_QUICKADD_OT_TYPE, + TR_QUICKADD_ROW_END, +) +QUICK_TRANSPORT_RANGE = create_range( + TR_SHEET, + TR_QUICKADD_TP_PAY, + TR_QUICKADD_ROW_START, + TR_QUICKADD_TP_TYPE, + TR_QUICKADD_ROW_END, +) diff --git a/bot/google_sheet_service/exceptions.py b/bot/google_sheet_service/exceptions.py new file mode 100644 index 0000000..e69de29 diff --git a/bot/google_sheet_service/sheets_api.py b/bot/google_sheet_service/sheets_api.py new file mode 100644 index 0000000..eec6833 --- /dev/null +++ b/bot/google_sheet_service/sheets_api.py @@ -0,0 +1,9 @@ +# sheets_api.py +from googleapiclient.discovery import build +from .auth import get_credentials + + +class SheetManager: + def __init__(self): + self.creds = get_credentials() + self.sheets_api = build("sheets", "v4", credentials=self.creds) From 2a461facbb9176d43afb8405347c971e03243990 Mon Sep 17 00:00:00 2001 From: brucewzj99 Date: Sat, 16 Mar 2024 02:57:19 +0800 Subject: [PATCH 2/3] some minor updates --- bot/google_sheet_service/auth.py | 14 ++ bot/google_sheet_service/exceptions.py | 58 +++++++++ bot/google_sheet_service/sheets_api.py | 9 -- .../{config.py => sheets_range.py} | 36 ++---- bot/google_sheet_service/sheets_services.py | 122 ++++++++++++++++++ bot/google_sheet_service/utils.py | 26 ++++ 6 files changed, 234 insertions(+), 31 deletions(-) delete mode 100644 bot/google_sheet_service/sheets_api.py rename bot/google_sheet_service/{config.py => sheets_range.py} (78%) create mode 100644 bot/google_sheet_service/sheets_services.py create mode 100644 bot/google_sheet_service/utils.py diff --git a/bot/google_sheet_service/auth.py b/bot/google_sheet_service/auth.py index 610ae12..2135996 100644 --- a/bot/google_sheet_service/auth.py +++ b/bot/google_sheet_service/auth.py @@ -1,9 +1,23 @@ +""" +auth.py + +This file contains a function that returns the credentials for the Google Sheets API. +The credentials are obtained from the GOOGLE_JSON environment variable, which is set in the .env file. +The get_credentials function uses the google.oauth2.service_account module to create a credentials +object from the service account info in the GOOGLE_JSON environment variable. +The credentials object is then returned to the caller. + +""" + import os import json from google.oauth2 import service_account def get_credentials(): + """ + Get credentials for the Google Sheets API. + """ SCOPES = ["https://www.googleapis.com/auth/spreadsheets"] google_json = os.getenv("GOOGLE_JSON") google_service = json.loads(google_json) diff --git a/bot/google_sheet_service/exceptions.py b/bot/google_sheet_service/exceptions.py index e69de29..cc8583c 100644 --- a/bot/google_sheet_service/exceptions.py +++ b/bot/google_sheet_service/exceptions.py @@ -0,0 +1,58 @@ +from googleapiclient.errors import HttpError +from functools import wraps + +# Custom exception classes defined here + + +def google_sheets_exception_handler(func): + @wraps(func) + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except HttpError as e: + if e.resp.status == 401: + raise AuthenticationError( + "Authentication failed: Check your credentials." + ) + elif e.resp.status == 404: + raise SpreadsheetNotFoundError( + "Spreadsheet not found: Check your spreadsheet ID." + ) + else: + raise APIRequestError( + f"API request failed with status {e.resp.status}: {e.error_details}" + ) + except Exception as e: + raise SheetsServiceError(f"An unexpected error occurred: {str(e)}") + + return wrapper + + +class SheetsServiceError(Exception): + """Base class for exceptions in this module.""" + + pass + + +class AuthenticationError(SheetsServiceError): + """Raised when there's an issue with authentication or credentials.""" + + pass + + +class SpreadsheetNotFoundError(SheetsServiceError): + """Raised when a specified spreadsheet cannot be found.""" + + pass + + +class APIRequestError(SheetsServiceError): + """Raised for errors related to making API requests to Google Sheets.""" + + pass + + +class InvalidEntryTypeError(SheetsServiceError): + """Raised when an invalid entry type is specified.""" + + pass diff --git a/bot/google_sheet_service/sheets_api.py b/bot/google_sheet_service/sheets_api.py deleted file mode 100644 index eec6833..0000000 --- a/bot/google_sheet_service/sheets_api.py +++ /dev/null @@ -1,9 +0,0 @@ -# sheets_api.py -from googleapiclient.discovery import build -from .auth import get_credentials - - -class SheetManager: - def __init__(self): - self.creds = get_credentials() - self.sheets_api = build("sheets", "v4", credentials=self.creds) diff --git a/bot/google_sheet_service/config.py b/bot/google_sheet_service/sheets_range.py similarity index 78% rename from bot/google_sheet_service/config.py rename to bot/google_sheet_service/sheets_range.py index 41e5001..298f40c 100644 --- a/bot/google_sheet_service/config.py +++ b/bot/google_sheet_service/sheets_range.py @@ -1,3 +1,13 @@ +""" +sheets_range.py + +This file contains the ranges for the Google Sheets Services for consistency. +The ranges are used to access specific cells in the Google Sheets. + +""" + +from bot.google_sheet_service.utils import create_range, create_complex_range + # Sheet names DD_SHEET = "Dropdown" # DD for Dropdown TR_SHEET = "Tracker" # TR for Tracker @@ -40,28 +50,6 @@ # Months OVERALL_RANGE = "!M13:O25" - -# Helper functions -def create_range(sheet, start_col, start_row, end_col=None, end_row=None): - """ - Create a standard range string. - Sample output: "Dropdown!A2:A9" - """ - end_part = f":{end_col}{end_row}" if end_col and end_row else "" - return f"{sheet}!{start_col}{start_row}{end_part}" - - -def create_complex_range(sheet, start_col_ord, end_col_ord, row_start, row_end): - """ - Create a range string for complex cases. - Sample output: ["Dropdown!B2:B9", "Dropdown!C2:C9", ...] - """ - return [ - f"{sheet}!{chr(i)}{row_start}:{chr(i)}{row_end}" - for i in range(start_col_ord, end_col_ord) - ] - - # Transport ranges TRANSPORT_RANGE = create_range( DD_SHEET, DD_TRANSPORT_COL, DD_SUBCAT_START, DD_TRANSPORT_COL, DD_SUBCAT_END @@ -71,6 +59,7 @@ def create_complex_range(sheet, start_col_ord, end_col_ord, row_start, row_end): OTHERS_MAIN_RANGE = create_range( DD_SHEET, DD_OTHERS_COL_START, DD_MAIN_CAT_ROW, DD_OTHERS_COL_END, DD_MAIN_CAT_ROW ) + OTHERS_SUB_RANGE = create_complex_range( DD_SHEET, ord(DD_OTHERS_COL_START), @@ -83,6 +72,7 @@ def create_complex_range(sheet, start_col_ord, end_col_ord, row_start, row_end): PAYMENT_MAIN_RANGE = create_range( DD_SHEET, DD_PAYMENT_COL_START, DD_MAIN_PAY_ROW, DD_PAYMENT_COL_END, DD_MAIN_PAY_ROW ) + PAYMENT_SUB_RANGE = create_complex_range( DD_SHEET, ord(DD_PAYMENT_COL_START), @@ -107,6 +97,7 @@ def create_complex_range(sheet, start_col_ord, end_col_ord, row_start, row_end): TR_QUICKADD_OT_TYPE, TR_QUICKADD_ROW_START, ) + QUICK_OTHERS_RANGE = create_range( TR_SHEET, TR_QUICKADD_OT_PAY, @@ -114,6 +105,7 @@ def create_complex_range(sheet, start_col_ord, end_col_ord, row_start, row_end): TR_QUICKADD_OT_TYPE, TR_QUICKADD_ROW_END, ) + QUICK_TRANSPORT_RANGE = create_range( TR_SHEET, TR_QUICKADD_TP_PAY, diff --git a/bot/google_sheet_service/sheets_services.py b/bot/google_sheet_service/sheets_services.py new file mode 100644 index 0000000..bac776f --- /dev/null +++ b/bot/google_sheet_service/sheets_services.py @@ -0,0 +1,122 @@ +""" +sheet_api.py + + +""" + +# sheets_api.py +from googleapiclient.discovery import build +from bot.common import EntryType +from bot.google_sheet_service.auth import get_credentials +from bot.google_sheet_service.sheets_range import * + + +class GoogleSheetsClient: + _instance = None + + @classmethod + def get_instance(cls): + if cls._instance is None: + creds = get_credentials() + cls._instance = build("sheets", "v4", credentials=creds) + return cls._instance + + +class DropdownManager: + """ + This class is responsible for managing the Dropdown sheet in the google sheet. + """ + + def __init__(self): + self.sheets_api = GoogleSheetsClient.get_instance() + + # to be rename as get_header_values + def get_main_dropdown_value(self, spreadsheet_id, entry_type) -> list[str]: + """ + This method gets the header values for the CATEGORY/PAYMENT of the transaction. + """ + range = [] + if entry_type == EntryType.TRANSPORT: + # actually if entry_type is transport shouldnt be calling this + # but instead call get_sub_dropdown_value instead + # will do something to make this change after refactoring + range = TRANSPORT_RANGE + elif entry_type == EntryType.OTHERS: + range = OTHERS_MAIN_RANGE + else: + range = PAYMENT_MAIN_RANGE + + results = ( + self.sheets_api.spreadsheets() + .values() + .get(spreadsheetId=spreadsheet_id, range=range) + .execute() + ) + + values_results = results.get("values", []) + + # to remove this and probably move it to get_sub_dropdown_value + if entry_type == EntryType.TRANSPORT: + results_list = [] + for sublist in values_results: + for item in sublist: + results_list.append(item) + return results_list + + return values_results[0] + + # to be rename as get_sub_values + def get_sub_dropdown_value( + self, spreadsheet_id, header_value, entry_type + ) -> list[str]: + range = [] + # if entry_type is transport, should be calling this instead of get_main_dropdown_value + if entry_type == EntryType.OTHERS: + range = OTHERS_SUB_RANGE + else: + range = PAYMENT_SUB_RANGE + results = ( + self.sheets_api.spreadsheets() + .values() + .batchGet(spreadsheetId=spreadsheet_id, ranges=range) + .execute() + ) + + value_results = results.get("valueRanges", []) + + dropdown = [] + for value in value_results: + if value.get("values", []): + if header_value == value.get("values", [])[0][0]: + dropdown.append(value.get("values", [])) + pass + + result_list = [item for sublist in dropdown[0] for item in sublist] + return result_list + + +class TrackerManager: + """ + This class is responsible for managing the Tracker sheet in the google sheet. + """ + + def __init__(self): + self.sheets_api = GoogleSheetsClient.get_instance() + + +class EntryManager: + """ + This class is responsible for logging of transactions in the google sheet. + """ + + def __init__(self): + self.sheets_api = GoogleSheetsClient.get_instance() + + +class SheetManager: + """ + This class is responsible for retrieving/moving the google sheet. + """ + + def __init__(self): + self.sheets_api = GoogleSheetsClient.get_instance() diff --git a/bot/google_sheet_service/utils.py b/bot/google_sheet_service/utils.py new file mode 100644 index 0000000..8d88d88 --- /dev/null +++ b/bot/google_sheet_service/utils.py @@ -0,0 +1,26 @@ +""" +utils.py + +This file contains utility functions for the Google Sheet Services. + +""" + + +def create_range(sheet, start_col, start_row, end_col=None, end_row=None): + """ + Create a standard range string. + Sample output: "Dropdown!A2:A9" + """ + end_part = f":{end_col}{end_row}" if end_col and end_row else "" + return f"{sheet}!{start_col}{start_row}{end_part}" + + +def create_complex_range(sheet, start_col_ord, end_col_ord, row_start, row_end): + """ + Create a range string for complex cases. + Sample output: ["Dropdown!B2:B9", "Dropdown!C2:C9", ...] + """ + return [ + f"{sheet}!{chr(i)}{row_start}:{chr(i)}{row_end}" + for i in range(start_col_ord, end_col_ord + 1) + ] From 12418216f35e6908e3e73b6f82af29bf382f7431 Mon Sep 17 00:00:00 2001 From: brucewzj99 Date: Sat, 16 Mar 2024 17:25:23 +0800 Subject: [PATCH 3/3] added new database module --- .../__init__.py | 0 bot/database_service/auth.py | 17 ++++ bot/database_service/firestore_service.py | 44 ++++++++++ bot/sheet_service/__init__.py | 0 .../auth.py | 0 .../exceptions.py | 0 .../sheets_range.py | 0 .../sheets_services.py | 85 +++++++++++++++++++ .../utils.py | 0 release_notes.md | 7 ++ 10 files changed, 153 insertions(+) rename bot/{google_sheet_service => database_service}/__init__.py (100%) create mode 100644 bot/database_service/auth.py create mode 100644 bot/database_service/firestore_service.py create mode 100644 bot/sheet_service/__init__.py rename bot/{google_sheet_service => sheet_service}/auth.py (100%) rename bot/{google_sheet_service => sheet_service}/exceptions.py (100%) rename bot/{google_sheet_service => sheet_service}/sheets_range.py (100%) rename bot/{google_sheet_service => sheet_service}/sheets_services.py (53%) rename bot/{google_sheet_service => sheet_service}/utils.py (100%) diff --git a/bot/google_sheet_service/__init__.py b/bot/database_service/__init__.py similarity index 100% rename from bot/google_sheet_service/__init__.py rename to bot/database_service/__init__.py diff --git a/bot/database_service/auth.py b/bot/database_service/auth.py new file mode 100644 index 0000000..927260e --- /dev/null +++ b/bot/database_service/auth.py @@ -0,0 +1,17 @@ +from firebase_admin import firestore +import firebase_admin +from firebase_admin import credentials +import os +import json + + +def get_db_client(): + """ + Get credentials for the Google Sheets API. + """ + firebase_json = json.loads(os.environ["FIREBASE_JSON"]) + + cred = credentials.Certificate(firebase_json) + firebase_admin.initialize_app(cred) + + return firestore.client() diff --git a/bot/database_service/firestore_service.py b/bot/database_service/firestore_service.py new file mode 100644 index 0000000..13a87af --- /dev/null +++ b/bot/database_service/firestore_service.py @@ -0,0 +1,44 @@ +from bot.database_service.auth import get_db_client + + +class FirestoreService: + """ + This class is responsible for managing the Firestore database. + """ + + def __init__(self): + self.db = get_db_client() + + # New user setup + def new_user_setup(self, telegram_id, sheet_id): + user_ref = self.db.collection("users").document(str(telegram_id)) + user_ref.set({"sheet_id": sheet_id}) + + # Check if user exists + def check_if_user_exists(self, telegram_id): + user_ref = self.db.collection("users").document(str(telegram_id)) + user_doc = user_ref.get() + return user_doc.exists + + # Get user sheet id + def get_user_sheet_id(self, telegram_id): + user_ref = self.db.collection("users").document(str(telegram_id)) + user_doc = user_ref.get() + if user_doc.exists: + return user_doc.get("sheet_id") + else: + return None + + # Get all user IDs + def get_all_user_id(self): + users_ref = self.db.collection("users") + user_ids = [int(user.id) for user in users_ref.stream()] + return user_ids + + # Get all sheet IDs + def get_all_sheet_id(self): + users_ref = self.db.collection("users") + sheet_ids = [] + for user in users_ref.stream(): + sheet_ids.append(user.get("sheet_id")) + return sheet_ids diff --git a/bot/sheet_service/__init__.py b/bot/sheet_service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bot/google_sheet_service/auth.py b/bot/sheet_service/auth.py similarity index 100% rename from bot/google_sheet_service/auth.py rename to bot/sheet_service/auth.py diff --git a/bot/google_sheet_service/exceptions.py b/bot/sheet_service/exceptions.py similarity index 100% rename from bot/google_sheet_service/exceptions.py rename to bot/sheet_service/exceptions.py diff --git a/bot/google_sheet_service/sheets_range.py b/bot/sheet_service/sheets_range.py similarity index 100% rename from bot/google_sheet_service/sheets_range.py rename to bot/sheet_service/sheets_range.py diff --git a/bot/google_sheet_service/sheets_services.py b/bot/sheet_service/sheets_services.py similarity index 53% rename from bot/google_sheet_service/sheets_services.py rename to bot/sheet_service/sheets_services.py index bac776f..83621f4 100644 --- a/bot/google_sheet_service/sheets_services.py +++ b/bot/sheet_service/sheets_services.py @@ -69,6 +69,9 @@ def get_main_dropdown_value(self, spreadsheet_id, entry_type) -> list[str]: def get_sub_dropdown_value( self, spreadsheet_id, header_value, entry_type ) -> list[str]: + """ + This method gets the sub values for the CATEGORY/PAYMENT of the transaction. + """ range = [] # if entry_type is transport, should be calling this instead of get_main_dropdown_value if entry_type == EntryType.OTHERS: @@ -112,6 +115,33 @@ class EntryManager: def __init__(self): self.sheets_api = GoogleSheetsClient.get_instance() + def create_entry(self, spreadsheet_id, month, row_tracker, row_data): + entry_type = row_data[0] + price = row_data[1].strip() + remarks = row_data[2].strip() + category = row_data[3].strip() + payment = row_data[4].strip() + + data = [price, remarks, category, payment] + sheet_column_start = "H" + sheet_column_end = "K" + if entry_type == EntryType.TRANSPORT: + remarks_list = [remark.strip() for remark in remarks.split(",")] + sheet_column_start = "C" + sheet_column_end = "G" + data = [price] + remarks_list + [category, payment] + + body = {"values": [data]} + range_name = ( + f"{month}!{sheet_column_start}{row_tracker}:{sheet_column_end}{row_tracker}" + ) + self.sheets_api.spreadsheets().values().update( + spreadsheetId=spreadsheet_id, + range=range_name, + valueInputOption="USER_ENTERED", + body=body, + ).execute() + class SheetManager: """ @@ -120,3 +150,58 @@ class SheetManager: def __init__(self): self.sheets_api = GoogleSheetsClient.get_instance() + + def get_last_entered_row(self, spreadsheet_id, month): + """ + This method gets the last entered row for the month. + """ + result = ( + self.sheets_api.spreadsheets() + .values() + .get(spreadsheetId=spreadsheet_id, range=f"{month}!A:K") + .execute() + ) + values = result.get("values", []) + return len(values) + + # to be rename as update_prev_day_total + def update_prev_day(self, spreadsheet_id, month, first_row, last_row=0): + """ + This method update the total amount spend for the previous day + """ + if last_row == 0: + last_row = self.get_last_entered_row(spreadsheet_id, month) + body = {"values": [[f"=SUM(C{first_row}:H{last_row})"]]} + range_name = f"{month}!B{first_row}" + self.sheets_api.spreadsheets().values().update( + spreadsheetId=spreadsheet_id, + range=range_name, + valueInputOption="USER_ENTERED", + body=body, + ).execute() + + def create_date(self, spreadsheet_id, day, month, first_row): + """ + This method creates the date for the day. + """ + body = {"values": [[day]]} + range_name = f"{month}!A{first_row}" + self.sheets_api.spreadsheets().values().update( + spreadsheetId=spreadsheet_id, + range=range_name, + valueInputOption="USER_ENTERED", + body=body, + ).execute() + + def get_sheet_id_by_title(self, spreadsheet_id, title_to_find): + sheet_metadata = ( + self.sheets_api.spreadsheets().get(spreadsheetId=spreadsheet_id).execute() + ) + sheets = sheet_metadata.get("sheets", "") + + for sheet in sheets: + title = sheet.get("properties", {}).get("title") + if title == title_to_find: + return sheet.get("properties", {}).get("sheetId") + + return None diff --git a/bot/google_sheet_service/utils.py b/bot/sheet_service/utils.py similarity index 100% rename from bot/google_sheet_service/utils.py rename to bot/sheet_service/utils.py diff --git a/release_notes.md b/release_notes.md index d5d110a..36c5181 100644 --- a/release_notes.md +++ b/release_notes.md @@ -1,4 +1,11 @@ # Release Notes +## Version 2.3.0 - Date 16 Mar 2024 +### For Developer 🧑‍💻 +- Create new database module which is currently in use for future maintenance and development purposes + +### Changes 🛠️ +- Censor URL for error message + ## Version 2.2.1 - Date 24 Feb 2024 ### Changes 🛠️ - Include error message when replying back to users