diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..5dfd76e --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,26 @@ +name: Python application test + +on: + push: + branches: [ '*' ] + +jobs: + build: + + runs-on: ubuntu-latest + environment: Test + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.9.13 + uses: actions/setup-python@v2 + with: + python-version: 3.9.13 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Run tests + env: + FIREBASE_JSON: ${{ secrets.FIREBASE_JSON }} + run: | + python -m pytest diff --git a/.gitignore b/.gitignore index 04ee175..8a4f8c5 100644 --- a/.gitignore +++ b/.gitignore @@ -161,4 +161,7 @@ cython_debug/ .vercel # My stuff -test_func.py \ No newline at end of file +test_func.py +users_backup.json +one_off_func.py +*.json \ No newline at end of file diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 0000000..24075a2 --- /dev/null +++ b/FAQ.md @@ -0,0 +1,27 @@ +## FAQ on TeleFinance Tracker Bot + +### 1. Sometimes the bot doesn't reply when I enter data. What should I do? +The bot may occasionally encounter issues due to hosting limitations. If it doesn't respond, try waiting, or use `/cancel` and re-enter the data. Most of the time, `/cancel` would solve majority of your issues! + +### 2. My entry ends up in the wrong category. How do I fix this? +When keying new entry, do ensure that you have received the "Transaction logged." message before adding a new one. For your current incorrect entries, you can manually adjust them in the Google Sheet. + +### 3. How do I add past transactions or entries for previous months? +For entries from past months, you can manually add them directly to the Google Sheet. For transactions earlier in the current month, use the `/backlog` command. + +### 4. How do I delete or edit a past entry? +To delete or edit past entries, manually adjust them in the Google Sheet. Remember not to shift the remaining entries up, as this could disrupt new entries. + +### 5. Can I add or edit quick settings for `/quickothers` or `/quicktransport`? +Yes, you can customize these settings by modifying the tracker tab in the Google Sheet. There is a limit set in the bot to prevent excessively long lists. + +### 6. How do I view all the commands available? +You can view all available commands by opening the menu in the Telegram chat or by typing `/help`. + +### 7. I edited the Google Sheet, but the bot doesn't seem to recognize the changes. What should I do? +Do ensure that `TRACKER` under the Tracker tab is correctly updated. The first row refers to the row number the first entry is on, the Transport Row and Other Row should refers to the last entry of the respective category. This means there's possiblity that the Other or Transport Row is -1 of first row. + +![tracker fixed](https://github.com/brucewzj99/tele-tracker-v2/doc-image/faq-tracker.png) + + +### If you have additional questions or need further assistance, feel free to [open a new issue](https://github.com/brucewzj99/tele-tracker-v2/issues) on GitHub. \ No newline at end of file diff --git a/LICENSE b/LICENSE index 5be567f..4fa6916 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Bruce Wang +Copyright (c) 2024 Bruce Wang Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 2d7fbbc..58319d9 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,9 @@ A python telegram bot to help track daily expenses onto google sheet, hosted on ## Release Notes You can find the release notes over [here](https://github.com/brucewzj99/tele-tracker-v2/blob/master/release_notes.md). +## FAQ +You can find the FAQ over [here](https://github.com/brucewzj99/tele-tracker-v2/blob/main/FAQ.md). + ## Table of Contents - [Getting Started (Users)](#getting-started-users) - [Getting Started (Developers)](#getting-started-developers) @@ -35,15 +38,14 @@ pip install -r requirements.txt * Set up Google Sheet API, download service account key * Retrieve Google Sheet API email * Set up Firebase Firestore Database, download service account key -* Retrieve your firebase database url * Set up telegram bot via [BotFather](https://t.me/BotFather) * Insert all of them into .env as follows, you can use py dotenv or set it as env variable in your venv * You can use the same TOKEN for BOT_TOKEN & TEST_TOKEN but I recommend using two different bots for testing and production +* I recommend having two different firebase databases for testing and production ``` .env BOT_TOKEN=your_bot_token TEST_TOKEN=your_test_token -DATABASE_URL=firebase_url GOOGLE_API_EMAIL=google_api_email FIREBASE_JSON=service_account_key GOOGLE_JSON=service_account_key @@ -53,9 +55,9 @@ MASTER_TELE_ID=your_telegram_id ### Step 3 * Proceed to project directory and run: ``` python -python3.9 test_polling.py +python3.9 dev_polling.py OR -python3.9 test_webhook.py +python3.9 dev_webhook.py ``` ## Usage diff --git a/bot/database_service/firestore_service.py b/bot/database_service/firestore_service.py index 7705049..beacf17 100644 --- a/bot/database_service/firestore_service.py +++ b/bot/database_service/firestore_service.py @@ -1,47 +1,93 @@ from bot.database_service.auth import get_db_client -from datetime import datetime +from datetime import datetime, timedelta +import pytz + class FirestoreService: """ This class is responsible for managing the Firestore database. """ - def __init__(self): + def __init__(self, collection_name="users"): self.db = get_db_client() + self.collection_name = collection_name # New user setup def new_user_setup(self, telegram_id, sheet_id, telegram_username): - user_ref = self.db.collection("users").document(str(telegram_id)) - user_ref.set({ - "sheet_id": sheet_id, - "datetime_created": datetime.now(), - "username": telegram_username - }) + user_ref = self.db.collection(self.collection_name).document(str(telegram_id)) + timestamp = datetime.now(pytz.timezone("Asia/Singapore")) + user_ref.set( + { + "sheet_id": sheet_id, + "datetime_created": timestamp, + "username": telegram_username, + "usage_count": 0, + "last_accessed": timestamp, + "hourly_accessed": timestamp, + "overusage_count": 0, + } + ) # Check if user exists def check_if_user_exists(self, telegram_id): - user_ref = self.db.collection("users").document(str(telegram_id)) + user_ref = self.db.collection(self.collection_name).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)) + def get_user_sheet_id(self, telegram_id, telegram_username): + user_ref = self.db.collection(self.collection_name).document(str(telegram_id)) user_doc = user_ref.get() + if user_doc.exists: - return user_doc.get("sheet_id") - else: - return None + try: + # Update username if it is different + if user_doc.get("username") != telegram_username: + user_ref.update({"username": telegram_username}) + + # Get the current time + now = datetime.now(pytz.timezone("Asia/Singapore")) + # Retrieve the hourly accessed time + hourly_accessed = user_doc.get("hourly_accessed") + + if not hourly_accessed: + hourly_accessed = now + + usage_count = user_doc.get("usage_count") + overusage_count = user_doc.get("overusage_count") + if (now - hourly_accessed) < timedelta(hours=1): + if usage_count < 30: + usage_count += 1 + else: + overusage_count += 1 # Increment overusage count if limit reached within the hour + else: + # Reset if a new hour has started + usage_count = 1 + hourly_accessed = now + + # Update the last accessed time, usage count and overusage count + user_ref.update( + { + "last_accessed": now, + "hourly_accessed": hourly_accessed, + "usage_count": usage_count, + "overusage_count": overusage_count, + } + ) + return user_doc.get("sheet_id") + except Exception as e: + raise e + return None # Get all user IDs def get_all_user_id(self): - users_ref = self.db.collection("users") + users_ref = self.db.collection(self.collection_name) 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") + users_ref = self.db.collection(self.collection_name) sheet_ids = [] for user in users_ref.stream(): sheet_ids.append(user.get("sheet_id")) diff --git a/bot/firestore_config.py b/bot/firestore_config.py deleted file mode 100644 index 4ad0fe5..0000000 --- a/bot/firestore_config.py +++ /dev/null @@ -1,12 +0,0 @@ -from firebase_admin import firestore -import firebase_admin -from firebase_admin import credentials -import os -import json - -firebase_json = json.loads(os.environ["FIREBASE_JSON"]) - -cred = credentials.Certificate(firebase_json) -firebase_admin.initialize_app(cred) - -db = firestore.client() diff --git a/bot/firestore_service.py b/bot/firestore_service.py deleted file mode 100644 index d2177ab..0000000 --- a/bot/firestore_service.py +++ /dev/null @@ -1,45 +0,0 @@ -from datetime import datetime -from bot.firestore_config import db - - -# New user setup -def new_user_setup(telegram_id, sheet_id, telegram_username): - user_ref = db.collection("users").document(str(telegram_id)) - user_ref.set({ - "sheet_id": sheet_id, - "datetime_created": datetime.now(), - "username": telegram_username - }) - - -# Check if user exists -def check_if_user_exists(telegram_id): - user_ref = 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(telegram_id): - user_ref = 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(): - users_ref = 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(): - users_ref = 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/telegram_bot.py b/bot/telegram_bot.py index f4118bd..1349c60 100644 --- a/bot/telegram_bot.py +++ b/bot/telegram_bot.py @@ -16,12 +16,14 @@ from bot.common import EntryType from bot.common import ConversationState as CS import bot.google_sheet_service as gs -import bot.firestore_service as db +from bot.database_service import firestore_service import bot.utils as utils +db = firestore_service.FirestoreService() timezone = pytz.timezone("Asia/Singapore") MASTER_TELE_ID = os.environ.get("MASTER_TELE_ID") + def get_category_text(sheet_id, entry_type): msg = "" markup_list = [] @@ -42,10 +44,13 @@ def get_payment_text(sheet_id): def start(update, context): context.user_data.clear() telegram_id = update.effective_user.id + telegram_username = update.effective_user.username try: user_exists = db.check_if_user_exists(telegram_id) if user_exists: - context.user_data["sheet_id"] = db.get_user_sheet_id(telegram_id) + context.user_data["sheet_id"] = db.get_user_sheet_id( + telegram_id, telegram_username + ) link = f"https://docs.google.com/spreadsheets/d/{context.user_data['sheet_id']}/edit" update.message.reply_text( f"Seems like you have already linked a Google sheet with us, do you want to link a different Google sheet with us?\n\n{link}", @@ -56,8 +61,9 @@ def start(update, context): update.message.reply_text(SETUP_TEXT, parse_mode=ParseMode.HTML) return CS.SET_UP except Exception as e: - - update.message.reply_text(ERROR_TEXT + "\nError:\n" + utils.sanitize_error_message(str(e))) + update.message.reply_text( + ERROR_TEXT + "\nError:\n" + utils.sanitize_error_message(str(e)) + ) return ConversationHandler.END @@ -128,7 +134,8 @@ def reset_up(update, context) -> int: def config(update, context): context.user_data.clear() telegram_id = update.effective_user.id - context.user_data["sheet_id"] = db.get_user_sheet_id(telegram_id) + telegram_username = update.effective_user.username + context.user_data["sheet_id"] = db.get_user_sheet_id(telegram_id, telegram_username) list = [ "Change Google Sheet", "Configure Quick Transport", @@ -179,7 +186,9 @@ def config_handler(update, context) -> int: ) return CS.CONFIG_SETUP except Exception as e: - update.callback_query.message.reply_text(ERROR_TEXT + "\nError:\n" +utils.sanitize_error_message(str(e))) + update.callback_query.message.reply_text( + ERROR_TEXT + "\nError:\n" + utils.sanitize_error_message(str(e)) + ) return ConversationHandler.END @@ -198,8 +207,10 @@ def config_setup(update, context) -> int: ) return CS.CONFIG_CATEGORY except Exception as e: - - update.callback_query.message.reply_text(ERROR_TEXT + "\nError:\n" +utils.sanitize_error_message(str(e))) + + update.callback_query.message.reply_text( + ERROR_TEXT + "\nError:\n" + utils.sanitize_error_message(str(e)) + ) return ConversationHandler.END update.callback_query.edit_message_text(END_TEXT, reply_markup=None) return ConversationHandler.END @@ -233,16 +244,18 @@ def config_category(update, context) -> int: ) return CS.CONFIG_SUBCATEGORY except Exception as e: - - update.callback_query.reply_text(ERROR_TEXT + "\nError:\n" +utils.sanitize_error_message(str(e))) + + update.callback_query.reply_text( + ERROR_TEXT + "\nError:\n" + utils.sanitize_error_message(str(e)) + ) return ConversationHandler.END def config_subcategory(update, context) -> int: reply = update.callback_query.data - context.user_data[ - "config-category" - ] = f'{context.user_data["config-category"]} - {reply}' + context.user_data["config-category"] = ( + f'{context.user_data["config-category"]} - {reply}' + ) try: sheet_id = context.user_data["sheet_id"] payment_list = gs.get_main_dropdown_value(sheet_id, "Payment") @@ -256,8 +269,10 @@ def config_subcategory(update, context) -> int: ) return CS.CONFIG_PAYMENT except Exception as e: - - update.callback_query.message.reply_text(ERROR_TEXT + "\nError:\n" +utils.sanitize_error_message(str(e))) + + update.callback_query.message.reply_text( + ERROR_TEXT + "\nError:\n" + utils.sanitize_error_message(str(e)) + ) return ConversationHandler.END @@ -276,16 +291,18 @@ def config_payment(update, context) -> int: ) return CS.CONFIG_SUBPAYMENT except Exception as e: - - update.callback_query.message.reply_text(ERROR_TEXT + "\nError:\n" +utils.sanitize_error_message(str(e))) + + update.callback_query.message.reply_text( + ERROR_TEXT + "\nError:\n" + utils.sanitize_error_message(str(e)) + ) return ConversationHandler.END def config_subpayment(update, context) -> int: reply = update.callback_query.data - context.user_data[ - "config-payment" - ] = f'{context.user_data["config-payment"]} - {reply}' + context.user_data["config-payment"] = ( + f'{context.user_data["config-payment"]} - {reply}' + ) try: update.callback_query.answer() update.callback_query.edit_message_text( @@ -302,15 +319,18 @@ def config_subpayment(update, context) -> int: ) return ConversationHandler.END except Exception as e: - - update.callback_query.message.reply_text(ERROR_TEXT + "\nError:\n" +utils.sanitize_error_message(str(e))) + + update.callback_query.message.reply_text( + ERROR_TEXT + "\nError:\n" + utils.sanitize_error_message(str(e)) + ) return ConversationHandler.END def add_entry(update, context): context.user_data.clear() telegram_id = update.effective_user.id - context.user_data["sheet_id"] = db.get_user_sheet_id(telegram_id) + telegram_username = update.effective_user.username + context.user_data["sheet_id"] = db.get_user_sheet_id(telegram_id, telegram_username) update.message.reply_text( ENTRY_TYPE_TEXT, reply_markup=utils.create_inline_markup( @@ -362,8 +382,10 @@ def remarks(update: Update, context) -> int: ) return CS.CATEGORY except Exception as e: - - update.message.reply_text(ERROR_TEXT + "\nError:\n" +utils.sanitize_error_message(str(e))) + + update.message.reply_text( + ERROR_TEXT + "\nError:\n" + utils.sanitize_error_message(str(e)) + ) return ConversationHandler.END @@ -407,8 +429,10 @@ def category(update, context) -> int: ) return CS.PAYMENT except Exception as e: - - update.callback_query.reply_text(ERROR_TEXT + "\nError:\n" +utils.sanitize_error_message(str(e))) + + update.callback_query.reply_text( + ERROR_TEXT + "\nError:\n" + utils.sanitize_error_message(str(e)) + ) return ConversationHandler.END @@ -434,8 +458,10 @@ def subcategory(update, context) -> int: ) return CS.PAYMENT except Exception as e: - - update.callback_query.message.reply_text(ERROR_TEXT + "\nError:\n" +utils.sanitize_error_message(str(e))) + + update.callback_query.message.reply_text( + ERROR_TEXT + "\nError:\n" + utils.sanitize_error_message(str(e)) + ) return ConversationHandler.END @@ -466,8 +492,10 @@ def payment(update, context) -> int: update.callback_query.message.reply_text("Transaction logged.") return ConversationHandler.END except Exception as e: - - update.callback_query.message.reply_text(ERROR_TEXT + "\nError:\n" +utils.sanitize_error_message(str(e))) + + update.callback_query.message.reply_text( + ERROR_TEXT + "\nError:\n" + utils.sanitize_error_message(str(e)) + ) return ConversationHandler.END @@ -494,8 +522,10 @@ def subpayment(update, context) -> int: return ConversationHandler.END except Exception as e: - - update.callback_query.message.reply_text(ERROR_TEXT + "\nError:\n" +utils.sanitize_error_message(str(e))) + + update.callback_query.message.reply_text( + ERROR_TEXT + "\nError:\n" + utils.sanitize_error_message(str(e)) + ) return ConversationHandler.END @@ -600,15 +630,20 @@ def cancel(update, context): def add_transport(update, context): context.user_data.clear() telegram_id = update.effective_user.id + telegram_username = update.effective_user.username try: - context.user_data["sheet_id"] = db.get_user_sheet_id(telegram_id) + context.user_data["sheet_id"] = db.get_user_sheet_id( + telegram_id, telegram_username + ) context.user_data["entry_type"] = EntryType.TRANSPORT setting_list = gs.get_quick_add_settings( context.user_data["sheet_id"], EntryType.TRANSPORT ) except Exception as e: - - update.callback_query.message.reply_text(ERROR_TEXT + "\nError:\n" +utils.sanitize_error_message(str(e))) + + update.callback_query.message.reply_text( + ERROR_TEXT + "\nError:\n" + utils.sanitize_error_message(str(e)) + ) return ConversationHandler.END if not setting_list or not setting_list[0]: update.message.reply_text(QUICK_SETUP_TRANSPORT) @@ -637,15 +672,20 @@ def add_transport(update, context): def add_others(update, context): context.user_data.clear() telegram_id = update.effective_user.id + telegram_username = update.effective_user.username try: - context.user_data["sheet_id"] = db.get_user_sheet_id(telegram_id) + context.user_data["sheet_id"] = db.get_user_sheet_id( + telegram_id, telegram_username + ) context.user_data["entry_type"] = EntryType.OTHERS setting_list = gs.get_quick_add_settings( context.user_data["sheet_id"], EntryType.OTHERS ) except Exception as e: - - update.callback_query.message.reply_text(ERROR_TEXT + "\nError:\n" +utils.sanitize_error_message(str(e))) + + update.callback_query.message.reply_text( + ERROR_TEXT + "\nError:\n" + utils.sanitize_error_message(str(e)) + ) return ConversationHandler.END if not setting_list or not setting_list[0]: update.message.reply_text(QUICK_SETUP_OTHER) @@ -693,10 +733,12 @@ def quick_add(update, context) -> int: update.message.reply_text("Transaction logged.") return ConversationHandler.END except Exception as e: - update.message.reply_text(ERROR_TEXT + "\nError:\n" +utils.sanitize_error_message(str(e))) + update.message.reply_text( + ERROR_TEXT + "\nError:\n" + utils.sanitize_error_message(str(e)) + ) return ConversationHandler.END except Exception as e: - + update.message.reply_text("Please follow the format and try again.") return CS.QUICK_ADD @@ -708,26 +750,36 @@ def help(update, context): def get_day_transaction(update, context): context.user_data.clear() telegram_id = update.effective_user.id + telegram_username = update.effective_user.username try: - context.user_data["sheet_id"] = db.get_user_sheet_id(telegram_id) + context.user_data["sheet_id"] = db.get_user_sheet_id( + telegram_id, telegram_username + ) update.message.reply_text(GET_TRANSACTION_TEXT) return CS.HANDLE_GET_TRANSACTION except Exception as e: - - update.message.reply_text(ERROR_TEXT + "\nError:\n" +utils.sanitize_error_message(str(e))) + + update.message.reply_text( + ERROR_TEXT + "\nError:\n" + utils.sanitize_error_message(str(e)) + ) return ConversationHandler.END def get_overall(update, context): context.user_data.clear() telegram_id = update.effective_user.id + telegram_username = update.effective_user.username try: - context.user_data["sheet_id"] = db.get_user_sheet_id(telegram_id) + context.user_data["sheet_id"] = db.get_user_sheet_id( + telegram_id, telegram_username + ) update.message.reply_text(GET_OVERALL_TEXT) return CS.HANDLE_GET_OVERALL except Exception as e: - - update.message.reply_text(ERROR_TEXT + "\nError:\n" +utils.sanitize_error_message(str(e))) + + update.message.reply_text( + ERROR_TEXT + "\nError:\n" + utils.sanitize_error_message(str(e)) + ) return ConversationHandler.END @@ -769,8 +821,10 @@ def handle_get_transaction(update, context): update.message.reply_text(GET_TRANSACTION_TEXT) return CS.HANDLE_GET_TRANSACTION except Exception as e: - - update.message.reply_text(ERROR_TEXT + "\nError:\n" +utils.sanitize_error_message(str(e))) + + update.message.reply_text( + ERROR_TEXT + "\nError:\n" + utils.sanitize_error_message(str(e)) + ) return ConversationHandler.END @@ -806,20 +860,27 @@ def handle_get_overall(update, context): update.message.reply_text(GET_OVERALL_TEXT) return CS.HANDLE_GET_OVERALL except Exception as e: - - update.message.reply_text(ERROR_TEXT + "\nError:\n" +utils.sanitize_error_message(str(e))) + + update.message.reply_text( + ERROR_TEXT + "\nError:\n" + utils.sanitize_error_message(str(e)) + ) return ConversationHandler.END def add_income(update, context): context.user_data.clear() telegram_id = update.effective_user.id + telegram_username = update.effective_user.username try: - context.user_data["sheet_id"] = db.get_user_sheet_id(telegram_id) + context.user_data["sheet_id"] = db.get_user_sheet_id( + telegram_id, telegram_username + ) update.message.reply_text(ADD_INCOME_TEXT) except Exception as e: - - update.message.reply_text(ERROR_TEXT + "\nError:\n" +utils.sanitize_error_message(str(e))) + + update.message.reply_text( + ERROR_TEXT + "\nError:\n" + utils.sanitize_error_message(str(e)) + ) return ConversationHandler.END return CS.INCOME @@ -847,8 +908,10 @@ def income(update, context) -> int: ) return CS.WORK_PLACE except Exception as e: - - update.message.reply_text(ERROR_TEXT + "\nError:\n" +utils.sanitize_error_message(str(e))) + + update.message.reply_text( + ERROR_TEXT + "\nError:\n" + utils.sanitize_error_message(str(e)) + ) return ConversationHandler.END @@ -881,8 +944,10 @@ def cpf(update, context) -> int: update.callback_query.message.reply_text(INCOME_LIMIT_TEXT) return ConversationHandler.END except Exception as e: - - update.callback_query.message.reply_text(ERROR_TEXT + "\nError:\n" +utils.sanitize_error_message(str(e))) + + update.callback_query.message.reply_text( + ERROR_TEXT + "\nError:\n" + utils.sanitize_error_message(str(e)) + ) return ConversationHandler.END @@ -907,7 +972,8 @@ def add_backlog_entry(update, context) -> int: return CS.ADD_BACKLOG_ENTRY telegram_id = update.effective_user.id - context.user_data["sheet_id"] = db.get_user_sheet_id(telegram_id) + telegram_username = update.effective_user.username + context.user_data["sheet_id"] = db.get_user_sheet_id(telegram_id, telegram_username) update.message.reply_text( ENTRY_TYPE_TEXT, reply_markup=utils.create_inline_markup( diff --git a/test_polling.py b/dev_polling.py similarity index 100% rename from test_polling.py rename to dev_polling.py diff --git a/test_webhook.py b/dev_webhook.py similarity index 100% rename from test_webhook.py rename to dev_webhook.py diff --git a/doc-image/faq-tracker.png b/doc-image/faq-tracker.png new file mode 100644 index 0000000..2a4682b Binary files /dev/null and b/doc-image/faq-tracker.png differ diff --git a/release_notes.md b/release_notes.md index 9769c93..2dec048 100644 --- a/release_notes.md +++ b/release_notes.md @@ -1,4 +1,14 @@ # Release Notes +## Version 2.3.4 - Date 3 May 2024 +### Enhancement 🔥 +- Added FAQ section + +### For Developer 🧑‍💻 +- Add accessed time and usage count to database for tracking purposes +- Rename `test_` prefix to `dev_` for developer testing +- Remove old firestore codes, use new database module +- Added test for database module + ## Version 2.3.3 - Date 1 May 2024 ### Minor Fix 🛠️ - Add datetime and username for logging purposes @@ -45,7 +55,7 @@ - Fix empty subcategory and subpayment bug ### New Features 🆕 -- Added a new shortcode to retrieve transaction for today `/today` +- Added a new shortcode to retrieve transaction for today ### For Developer 🧑‍💻 - Mini refactor of code, added utils folder to store helper functions diff --git a/requirements.txt b/requirements.txt index 29465d9..31f9d98 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,7 @@ charset-normalizer==2.0.12 click==8.1.3 colorama==0.4.6 cryptography==40.0.2 +exceptiongroup==1.2.1 firebase-admin==6.1.0 Flask==2.2.2 google-api-core==2.11.0 @@ -25,10 +26,13 @@ grpcio-status==1.54.2 httplib2==0.22.0 idna==3.4 importlib-metadata==6.6.0 +iniconfig==2.0.0 itsdangerous==2.1.2 Jinja2==3.1.2 MarkupSafe==2.1.2 msgpack==1.0.5 +packaging==24.0 +pluggy==1.5.0 proto-plus==1.22.2 protobuf==4.23.2 pyasn1==0.5.0 @@ -37,12 +41,15 @@ pycparser==2.21 PyJWT==2.7.0 pyngrok==6.0.0 pyparsing==3.0.9 +pytest==8.2.0 +python-dotenv==1.0.1 python-telegram-bot==13.7 pytz==2023.3 PyYAML==6.0 requests==2.26.0 rsa==4.9 six==1.16.0 +tomli==2.0.1 tornado==6.3.2 typing_extensions==4.6.2 tzdata==2023.3 diff --git a/test/test_database.py b/test/test_database.py new file mode 100644 index 0000000..60be571 --- /dev/null +++ b/test/test_database.py @@ -0,0 +1,27 @@ +from bot.database_service import firestore_service + +from unittest import TestCase + + +class TestFirestoreService(TestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.db = firestore_service.FirestoreService() + cls.telegram_id = "123456" + cls.sheet_id = "sheet123" + cls.telegram_username = "test_user" + + def test_check_if_user_exists(self): + # Act + user_exists = self.db.check_if_user_exists(self.telegram_id) + + # Assert + self.assertTrue(user_exists) + + def test_get_user_sheet_id(self): + # Act + sheet_id = self.db.get_user_sheet_id(self.telegram_id, self.telegram_username) + + # Assert + self.assertEqual(sheet_id, self.sheet_id)