From 19b9f7fd28b67d864df1771ef292f1e147c3f4bb Mon Sep 17 00:00:00 2001 From: Karl Date: Fri, 9 May 2025 16:31:14 +0100 Subject: [PATCH] cwc --- .gitignore | 4 +- .vscode/launch.json | 32 +-- app.py | 372 +++++++++++++++++------------------ backend/app.py | 38 ++-- backend/lib/mysql.py | 74 +++---- config.py.sample | 22 +-- lib/datetime.py | 92 ++++----- lib/reqs.py | 214 ++++++++++---------- static/service-worker.js | 28 +-- static/styles.css | 60 +++--- templates/add_account.html | 212 ++++++++++---------- templates/home.html | 186 +++++++++--------- templates/index.html | 160 +++++++-------- templates/urls.html | 130 ++++++------ templates/user_accounts.html | 240 +++++++++++----------- 15 files changed, 932 insertions(+), 932 deletions(-) diff --git a/.gitignore b/.gitignore index a11ea35..87c1c1e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -venv/ -config.py +venv/ +config.py **/*.pyc \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 669e8d6..7a7c9ea 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,17 +1,17 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Python Debugger: Current File", - "type": "debugpy", - "request": "launch", - "program": "${file}", - "console": "integratedTerminal", - "justMyCode": false, - "args": ["--host=0.0.0.0"] - } - ] +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python Debugger: Current File", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "justMyCode": false, + "args": ["--host=0.0.0.0"] + } + ] } \ No newline at end of file diff --git a/app.py b/app.py index c6ff6e5..1e5b844 100644 --- a/app.py +++ b/app.py @@ -1,186 +1,186 @@ -# app.py -from flask import Flask, render_template, request, redirect, url_for, session, send_file, jsonify -from flask_caching import Cache -import requests.auth -import os -from lib.datetime import filter_accounts_next_30_days, filter_accounts_expired -from lib.reqs import get_urls, get_user_accounts, add_user_account, delete_user_account, get_user_accounts_count -from flask import send_from_directory -import requests -import base64 -from flask import Flask -from config import DevelopmentConfig -from paddleocr import PaddleOCR -from PIL import Image -import numpy as np - -os.environ["OMP_NUM_THREADS"] = "1" -os.environ["MKL_NUM_THREADS"] = "1" - -app = Flask(__name__) -app.config.from_object( - DevelopmentConfig -) -cache = Cache(app, config={"CACHE_TYPE": "SimpleCache"}) - -ocr = PaddleOCR(use_angle_cls=True, lang='en') # Adjust language if needed - -app.config['SESSION_COOKIE_SECURE'] = True # Only send cookie over HTTPS -app.config['SESSION_COOKIE_HTTPONLY'] = True # Prevent JavaScript access -app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # Adjust for cross-site requests -app.config['PERMANENT_SESSION_LIFETIME'] = 60 * 60 * 24 * 365 # 1 year in seconds -cache.clear() # Clears all cache entries - -@app.before_request -def make_session_permanent(): - session.permanent = True - -@app.route('/manifest.json') -def serve_manifest(): - return send_file('manifest.json', mimetype='application/manifest+json') - -@app.route("/favicon.ico") -def favicon(): - return send_from_directory( - os.path.join(app.root_path, "static"), - "favicon.ico", - mimetype="image/vnd.microsoft.icon", - ) - - -@app.route("/") -def index(): - # If the user is logged in, redirect to a protected page like /accounts - if session.get("logged_in"): - return redirect(url_for("home")) - return render_template("index.html") - - -@app.route("/home") -@cache.cached(timeout=60) # cache for 120 seconds -def home(): - if session.get("logged_in"): - base_url = app.config["BASE_URL"] # Access base_url from the config - all_accounts = get_user_accounts(base_url, session["auth_credentials"]) - count = len(all_accounts) - current_month_accounts = filter_accounts_next_30_days(all_accounts) - expired_accounts = filter_accounts_expired(all_accounts) - return render_template( - "home.html", - username=session["username"], - accounts=count, - current_month_accounts=current_month_accounts, - expired_accounts=expired_accounts, - ) - return render_template("index.html") - - -@app.route("/login", methods=["POST"]) -def login(): - username = request.form["username"] - password = request.form["password"] - - # Encode the username and password in Base64 - credentials = f"{username}:{password}" - encoded_credentials = base64.b64encode(credentials.encode()).decode() - - base_url = app.config["BASE_URL"] # Access base_url from the config - login_url = f"{base_url}/Login" # Construct the full URL - - # Send GET request to the external login API with Basic Auth - response = requests.get( - login_url, auth=requests.auth.HTTPBasicAuth(username, password) - ) - - # Check if login was successful - if response.status_code == 200 and response.json().get("auth") == "Success": - # Set session variable to indicate the user is logged in - session["logged_in"] = True - session["username"] = username - session["auth_credentials"] = encoded_credentials - return redirect(url_for("home")) # Redirect to the Accounts page - else: - # Show error on the login page - error = "Invalid username or password. Please try again." - return render_template("index.html", error=error) - - -@app.route("/urls", methods=["GET"]) -@cache.cached(timeout=300) # cache for 5 minutes -def urls(): - # Check if the user is logged in - if not session.get("logged_in"): - return redirect(url_for("home")) - # Placeholder content for Accounts page - base_url = app.config["BASE_URL"] # Access base_url from the config - return render_template( - "urls.html", urls=get_urls(base_url, session["auth_credentials"]) - ) - - -@app.route("/accounts", methods=["GET"]) -@cache.cached(timeout=120) # cache for 120 seconds -def user_accounts(): - # Check if the user is logged in - if not session.get("logged_in"): - return redirect(url_for("home")) - # Placeholder content for Accounts page - base_url = app.config["BASE_URL"] # Access base_url from the config - return render_template( - "user_accounts.html", - username=session["username"], - user_accounts=get_user_accounts(base_url, session["auth_credentials"]), - auth=session["auth_credentials"], - ) - - -@app.route("/accounts/add", methods=["GET", "POST"]) -def add_account(): - base_url = app.config["BASE_URL"] # Access base_url from the config - if request.method == "POST": - username = request.form["username"] - password = request.form["password"] - stream = request.form["stream"] - - if add_user_account( - base_url, session["auth_credentials"], username, password, stream - ): - cache.clear() # Clears all cache entries - return redirect(url_for("user_accounts")) - return render_template("add_account.html") - - return render_template("add_account.html") - - -@app.route("/accounts/delete", methods=["POST"]) -def delete_account(): - stream = request.form.get("stream") - username = request.form.get("username") - base_url = app.config["BASE_URL"] - - if delete_user_account(base_url, session["auth_credentials"], stream, username): - cache.clear() # Clears all cache entries - return redirect(url_for("user_accounts")) - return redirect(url_for("user_accounts")) - - -@app.route('/OCRupload', methods=['POST']) -def OCRupload(): - if 'image' not in request.files: - return jsonify({"error": "No image file found"}), 400 - # Get the uploaded file - file = request.files['image'] - try: - image = Image.open(file.stream) - image_np = np.array(image) - result = ocr.ocr(image_np) - # Extract text - extracted_text = [] - for line in result[0]: - extracted_text.append(line[1][0]) - return render_template("add_account.html", username=extracted_text[2], password=extracted_text[3]) - except Exception as e: - return jsonify({"error": str(e)}), 500 - -if __name__ == "__main__": - app.run(debug=app.config["DEBUG"], host=app.config["HOST"], port=app.config["PORT"]) +# app.py +from flask import Flask, render_template, request, redirect, url_for, session, send_file, jsonify +from flask_caching import Cache +import requests.auth +import os +from lib.datetime import filter_accounts_next_30_days, filter_accounts_expired +from lib.reqs import get_urls, get_user_accounts, add_user_account, delete_user_account, get_user_accounts_count +from flask import send_from_directory +import requests +import base64 +from flask import Flask +from config import DevelopmentConfig +from paddleocr import PaddleOCR +from PIL import Image +import numpy as np + +os.environ["OMP_NUM_THREADS"] = "1" +os.environ["MKL_NUM_THREADS"] = "1" + +app = Flask(__name__) +app.config.from_object( + DevelopmentConfig +) +cache = Cache(app, config={"CACHE_TYPE": "SimpleCache"}) + +ocr = PaddleOCR(use_angle_cls=True, lang='en') # Adjust language if needed + +app.config['SESSION_COOKIE_SECURE'] = True # Only send cookie over HTTPS +app.config['SESSION_COOKIE_HTTPONLY'] = True # Prevent JavaScript access +app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # Adjust for cross-site requests +app.config['PERMANENT_SESSION_LIFETIME'] = 60 * 60 * 24 * 365 # 1 year in seconds +cache.clear() # Clears all cache entries + +@app.before_request +def make_session_permanent(): + session.permanent = True + +@app.route('/manifest.json') +def serve_manifest(): + return send_file('manifest.json', mimetype='application/manifest+json') + +@app.route("/favicon.ico") +def favicon(): + return send_from_directory( + os.path.join(app.root_path, "static"), + "favicon.ico", + mimetype="image/vnd.microsoft.icon", + ) + + +@app.route("/") +def index(): + # If the user is logged in, redirect to a protected page like /accounts + if session.get("logged_in"): + return redirect(url_for("home")) + return render_template("index.html") + + +@app.route("/home") +@cache.cached(timeout=60) # cache for 120 seconds +def home(): + if session.get("logged_in"): + base_url = app.config["BASE_URL"] # Access base_url from the config + all_accounts = get_user_accounts(base_url, session["auth_credentials"]) + count = len(all_accounts) + current_month_accounts = filter_accounts_next_30_days(all_accounts) + expired_accounts = filter_accounts_expired(all_accounts) + return render_template( + "home.html", + username=session["username"], + accounts=count, + current_month_accounts=current_month_accounts, + expired_accounts=expired_accounts, + ) + return render_template("index.html") + + +@app.route("/login", methods=["POST"]) +def login(): + username = request.form["username"] + password = request.form["password"] + + # Encode the username and password in Base64 + credentials = f"{username}:{password}" + encoded_credentials = base64.b64encode(credentials.encode()).decode() + + base_url = app.config["BASE_URL"] # Access base_url from the config + login_url = f"{base_url}/Login" # Construct the full URL + + # Send GET request to the external login API with Basic Auth + response = requests.get( + login_url, auth=requests.auth.HTTPBasicAuth(username, password) + ) + + # Check if login was successful + if response.status_code == 200 and response.json().get("auth") == "Success": + # Set session variable to indicate the user is logged in + session["logged_in"] = True + session["username"] = username + session["auth_credentials"] = encoded_credentials + return redirect(url_for("home")) # Redirect to the Accounts page + else: + # Show error on the login page + error = "Invalid username or password. Please try again." + return render_template("index.html", error=error) + + +@app.route("/urls", methods=["GET"]) +@cache.cached(timeout=300) # cache for 5 minutes +def urls(): + # Check if the user is logged in + if not session.get("logged_in"): + return redirect(url_for("home")) + # Placeholder content for Accounts page + base_url = app.config["BASE_URL"] # Access base_url from the config + return render_template( + "urls.html", urls=get_urls(base_url, session["auth_credentials"]) + ) + + +@app.route("/accounts", methods=["GET"]) +@cache.cached(timeout=120) # cache for 120 seconds +def user_accounts(): + # Check if the user is logged in + if not session.get("logged_in"): + return redirect(url_for("home")) + # Placeholder content for Accounts page + base_url = app.config["BASE_URL"] # Access base_url from the config + return render_template( + "user_accounts.html", + username=session["username"], + user_accounts=get_user_accounts(base_url, session["auth_credentials"]), + auth=session["auth_credentials"], + ) + + +@app.route("/accounts/add", methods=["GET", "POST"]) +def add_account(): + base_url = app.config["BASE_URL"] # Access base_url from the config + if request.method == "POST": + username = request.form["username"] + password = request.form["password"] + stream = request.form["stream"] + + if add_user_account( + base_url, session["auth_credentials"], username, password, stream + ): + cache.clear() # Clears all cache entries + return redirect(url_for("user_accounts")) + return render_template("add_account.html") + + return render_template("add_account.html") + + +@app.route("/accounts/delete", methods=["POST"]) +def delete_account(): + stream = request.form.get("stream") + username = request.form.get("username") + base_url = app.config["BASE_URL"] + + if delete_user_account(base_url, session["auth_credentials"], stream, username): + cache.clear() # Clears all cache entries + return redirect(url_for("user_accounts")) + return redirect(url_for("user_accounts")) + + +@app.route('/OCRupload', methods=['POST']) +def OCRupload(): + if 'image' not in request.files: + return jsonify({"error": "No image file found"}), 400 + # Get the uploaded file + file = request.files['image'] + try: + image = Image.open(file.stream) + image_np = np.array(image) + result = ocr.ocr(image_np) + # Extract text + extracted_text = [] + for line in result[0]: + extracted_text.append(line[1][0]) + return render_template("add_account.html", username=extracted_text[2], password=extracted_text[3]) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +if __name__ == "__main__": + app.run(debug=app.config["DEBUG"], host=app.config["HOST"], port=app.config["PORT"]) diff --git a/backend/app.py b/backend/app.py index 5328312..b711923 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,19 +1,19 @@ -from flask import Flask, jsonify -from config import DevelopmentConfig -from lib.mysql import execute_query - -app = Flask(__name__) -app.config.from_object(DevelopmentConfig) - -@app.route('/getUserAccounts', methods=['GET']) -def get_user_accounts(): - # Use the execute_query function to get user accounts - - data = execute_query("SELECT COUNT(*) AS account_count FROM userAccounts WHERE userID = %s;", (1,)) - if data is None: - return jsonify({"error": "Database query failed"}), 500 - return jsonify(data), 200 - -# Run the app -if __name__ == '__main__': - app.run(debug=app.config["DEBUG"], port=app.config["PORT"]) +from flask import Flask, jsonify +from config import DevelopmentConfig +from lib.mysql import execute_query + +app = Flask(__name__) +app.config.from_object(DevelopmentConfig) + +@app.route('/getUserAccounts', methods=['GET']) +def get_user_accounts(): + # Use the execute_query function to get user accounts + + data = execute_query("SELECT COUNT(*) AS account_count FROM userAccounts WHERE userID = %s;", (1,)) + if data is None: + return jsonify({"error": "Database query failed"}), 500 + return jsonify(data), 200 + +# Run the app +if __name__ == '__main__': + app.run(debug=app.config["DEBUG"], port=app.config["PORT"]) diff --git a/backend/lib/mysql.py b/backend/lib/mysql.py index 7450411..6279714 100644 --- a/backend/lib/mysql.py +++ b/backend/lib/mysql.py @@ -1,37 +1,37 @@ -import mysql.connector -from flask import current_app - -def execute_query(query, params=None, fetch_one=False): - """Execute a SQL query and optionally fetch results.""" - try: - # Get database configuration from the current app context - db_config = { - "host": current_app.config['DBHOST'], - "user": current_app.config['DBUSER'], - "password": current_app.config['DBPASS'], - "database": current_app.config['DATABASE'], - } - - # Establish database connection - connection = mysql.connector.connect(**db_config) - cursor = connection.cursor(dictionary=True) - - # Execute the query with optional parameters - cursor.execute(query, params) - - # Fetch results if it's a SELECT query - if query.strip().upper().startswith("SELECT"): - result = cursor.fetchone() if fetch_one else cursor.fetchall() - else: - # Commit changes for INSERT, UPDATE, DELETE - connection.commit() - result = cursor.rowcount # Number of affected rows - - # Close the database connection - cursor.close() - connection.close() - - return result - except mysql.connector.Error as err: - print("Error: ", err) - return None +import mysql.connector +from flask import current_app + +def execute_query(query, params=None, fetch_one=False): + """Execute a SQL query and optionally fetch results.""" + try: + # Get database configuration from the current app context + db_config = { + "host": current_app.config['DBHOST'], + "user": current_app.config['DBUSER'], + "password": current_app.config['DBPASS'], + "database": current_app.config['DATABASE'], + } + + # Establish database connection + connection = mysql.connector.connect(**db_config) + cursor = connection.cursor(dictionary=True) + + # Execute the query with optional parameters + cursor.execute(query, params) + + # Fetch results if it's a SELECT query + if query.strip().upper().startswith("SELECT"): + result = cursor.fetchone() if fetch_one else cursor.fetchall() + else: + # Commit changes for INSERT, UPDATE, DELETE + connection.commit() + result = cursor.rowcount # Number of affected rows + + # Close the database connection + cursor.close() + connection.close() + + return result + except mysql.connector.Error as err: + print("Error: ", err) + return None diff --git a/config.py.sample b/config.py.sample index fc4419e..863df00 100644 --- a/config.py.sample +++ b/config.py.sample @@ -1,11 +1,11 @@ -# config.py - -class Config: - DEBUG = False - BASE_URL = '' # Set your base URL here - -class DevelopmentConfig(Config): - DEBUG = True - -class ProductionConfig(Config): - BASE_URL = '' # Production base URL +# config.py + +class Config: + DEBUG = False + BASE_URL = '' # Set your base URL here + +class DevelopmentConfig(Config): + DEBUG = True + +class ProductionConfig(Config): + BASE_URL = '' # Production base URL diff --git a/lib/datetime.py b/lib/datetime.py index dc85d2d..48efe5a 100644 --- a/lib/datetime.py +++ b/lib/datetime.py @@ -1,46 +1,46 @@ -from datetime import datetime, timedelta -from typing import List, Dict - -def filter_accounts_next_30_days(accounts: List[Dict[str, int]]) -> List[Dict[str, int]]: - """Filter accounts whose expiry date falls within the next 30 days. - - Args: - accounts (List[Dict[str, int]]): A list of account dictionaries, each containing - an 'expiaryDate' key with an epoch timestamp as its value. - - Returns: - List[Dict[str, int]]: A list of accounts expiring within the next 30 days. - """ - now = datetime.now() - thirty_days_later = now + timedelta(days=30) - - # Convert current time and 30 days later to epoch timestamps - now_timestamp = int(now.timestamp()) - thirty_days_later_timestamp = int(thirty_days_later.timestamp()) - - # Filter accounts with expiryDate within the next 30 days - return [ - account for account in accounts - if now_timestamp <= account['expiaryDate'] < thirty_days_later_timestamp - ] - -def filter_accounts_expired(accounts: List[Dict[str, int]]) -> List[Dict[str, int]]: - """Filter accounts whose expiry date has passed. - - Args: - accounts (List[Dict[str, int]]): A list of account dictionaries, each containing - an 'expiaryDate' key with an epoch timestamp as its value. - - Returns: - List[Dict[str, int]]: A list of accounts that have expired. - """ - # Get the current epoch timestamp - current_timestamp = int(datetime.now().timestamp()) - - # Filter accounts where the current date is greater than the expiryDate - expired_accounts = [ - account for account in accounts - if account['expiaryDate'] < current_timestamp - ] - - return expired_accounts +from datetime import datetime, timedelta +from typing import List, Dict + +def filter_accounts_next_30_days(accounts: List[Dict[str, int]]) -> List[Dict[str, int]]: + """Filter accounts whose expiry date falls within the next 30 days. + + Args: + accounts (List[Dict[str, int]]): A list of account dictionaries, each containing + an 'expiaryDate' key with an epoch timestamp as its value. + + Returns: + List[Dict[str, int]]: A list of accounts expiring within the next 30 days. + """ + now = datetime.now() + thirty_days_later = now + timedelta(days=30) + + # Convert current time and 30 days later to epoch timestamps + now_timestamp = int(now.timestamp()) + thirty_days_later_timestamp = int(thirty_days_later.timestamp()) + + # Filter accounts with expiryDate within the next 30 days + return [ + account for account in accounts + if now_timestamp <= account['expiaryDate'] < thirty_days_later_timestamp + ] + +def filter_accounts_expired(accounts: List[Dict[str, int]]) -> List[Dict[str, int]]: + """Filter accounts whose expiry date has passed. + + Args: + accounts (List[Dict[str, int]]): A list of account dictionaries, each containing + an 'expiaryDate' key with an epoch timestamp as its value. + + Returns: + List[Dict[str, int]]: A list of accounts that have expired. + """ + # Get the current epoch timestamp + current_timestamp = int(datetime.now().timestamp()) + + # Filter accounts where the current date is greater than the expiryDate + expired_accounts = [ + account for account in accounts + if account['expiaryDate'] < current_timestamp + ] + + return expired_accounts diff --git a/lib/reqs.py b/lib/reqs.py index 69514b4..3e56419 100644 --- a/lib/reqs.py +++ b/lib/reqs.py @@ -1,107 +1,107 @@ -import requests -import json -from datetime import datetime -from typing import List, Dict, Any - - -def get_urls(base_url: str, auth: str) -> List[Dict[str, Any]]: - """Retrieve user account streams from the specified base URL. - - Args: - base_url (str): The base URL of the API. - auth (str): The authorization token for accessing the API. - - Returns: - List[Dict[str, Any]]: A list of user account streams. - """ - url = f"{base_url}/getUserAccounts/streams" - payload = {} - headers = {"Authorization": f"Basic {auth}"} - - response = requests.request("GET", url, headers=headers, data=payload) - return json.loads(response.text) - - -def get_user_accounts(base_url: str, auth: str) -> List[Dict[str, Any]]: - """Retrieve user accounts from the specified base URL. - - Args: - base_url (str): The base URL of the API. - auth (str): The authorization token for accessing the API. - - Returns: - List[Dict[str, Any]]: A list of user accounts with their expiration dates rendered. - """ - url = f"{base_url}/getUserAccounts" - payload = {} - headers = {"Authorization": f"Basic {auth}"} - - response = requests.request("GET", url, headers=headers, data=payload) - res_json = json.loads(response.text) - - for account in res_json: - account["expiaryDate_rendered"] = datetime.utcfromtimestamp( - account["expiaryDate"] - ).strftime("%d/%m/%Y") - - return res_json - - -def delete_user_account(base_url: str, auth: str, stream: str, username: str) -> bool: - """Delete a user account from the specified base URL. - - Args: - base_url (str): The base URL of the API. - auth (str): The authorization token for accessing the API. - stream (str): The name of the stream associated with the user account. - username (str): The username of the account to delete. - - Returns: - bool: True if the account was deleted successfully, False otherwise. - """ - url = f"{base_url}/deleteAccount" - payload = {"stream": stream, "user": username} - headers = {"Authorization": f"Basic {auth}"} - - response = requests.request("POST", url, headers=headers, data=payload) - return "Deleted" in response.text - - -def add_user_account(base_url: str, auth: str, username: str, password: str, stream: str) -> bool: - """Add a user account to the specified base URL. - - Args: - base_url (str): The base URL of the API. - auth (str): The authorization token for accessing the API. - username (str): The username of the account to add. - password (str): The password of the account to add. - stream (str): The name of the stream associated with the user account. - - Returns: - bool: True if the account was added successfully, False otherwise. - """ - url = f"{base_url}/addAccount" - payload = {"username": username, "password": password, "stream": stream} - headers = {"Authorization": f"Basic {auth}"} - - response = requests.request("POST", url, headers=headers, data=payload) - return "Added successfully" in response.text - - -def get_user_accounts_count(base_url: str, auth: str) -> int: - """Get the count of user accounts from the specified base URL. - - Args: - base_url (str): The base URL of the API. - auth (str): The authorization token for accessing the API. - - Returns: - int: The count of user accounts. - """ - url = f"{base_url}/getUserAccounts/count" - payload = {} - headers = {"Authorization": f"Basic {auth}"} - - response = requests.request("GET", url, headers=headers, data=payload) - res_json = json.loads(response.text) - return res_json['count'] +import requests +import json +from datetime import datetime +from typing import List, Dict, Any + + +def get_urls(base_url: str, auth: str) -> List[Dict[str, Any]]: + """Retrieve user account streams from the specified base URL. + + Args: + base_url (str): The base URL of the API. + auth (str): The authorization token for accessing the API. + + Returns: + List[Dict[str, Any]]: A list of user account streams. + """ + url = f"{base_url}/getUserAccounts/streams" + payload = {} + headers = {"Authorization": f"Basic {auth}"} + + response = requests.request("GET", url, headers=headers, data=payload) + return json.loads(response.text) + + +def get_user_accounts(base_url: str, auth: str) -> List[Dict[str, Any]]: + """Retrieve user accounts from the specified base URL. + + Args: + base_url (str): The base URL of the API. + auth (str): The authorization token for accessing the API. + + Returns: + List[Dict[str, Any]]: A list of user accounts with their expiration dates rendered. + """ + url = f"{base_url}/getUserAccounts" + payload = {} + headers = {"Authorization": f"Basic {auth}"} + + response = requests.request("GET", url, headers=headers, data=payload) + res_json = json.loads(response.text) + + for account in res_json: + account["expiaryDate_rendered"] = datetime.utcfromtimestamp( + account["expiaryDate"] + ).strftime("%d/%m/%Y") + + return res_json + + +def delete_user_account(base_url: str, auth: str, stream: str, username: str) -> bool: + """Delete a user account from the specified base URL. + + Args: + base_url (str): The base URL of the API. + auth (str): The authorization token for accessing the API. + stream (str): The name of the stream associated with the user account. + username (str): The username of the account to delete. + + Returns: + bool: True if the account was deleted successfully, False otherwise. + """ + url = f"{base_url}/deleteAccount" + payload = {"stream": stream, "user": username} + headers = {"Authorization": f"Basic {auth}"} + + response = requests.request("POST", url, headers=headers, data=payload) + return "Deleted" in response.text + + +def add_user_account(base_url: str, auth: str, username: str, password: str, stream: str) -> bool: + """Add a user account to the specified base URL. + + Args: + base_url (str): The base URL of the API. + auth (str): The authorization token for accessing the API. + username (str): The username of the account to add. + password (str): The password of the account to add. + stream (str): The name of the stream associated with the user account. + + Returns: + bool: True if the account was added successfully, False otherwise. + """ + url = f"{base_url}/addAccount" + payload = {"username": username, "password": password, "stream": stream} + headers = {"Authorization": f"Basic {auth}"} + + response = requests.request("POST", url, headers=headers, data=payload) + return "Added successfully" in response.text + + +def get_user_accounts_count(base_url: str, auth: str) -> int: + """Get the count of user accounts from the specified base URL. + + Args: + base_url (str): The base URL of the API. + auth (str): The authorization token for accessing the API. + + Returns: + int: The count of user accounts. + """ + url = f"{base_url}/getUserAccounts/count" + payload = {} + headers = {"Authorization": f"Basic {auth}"} + + response = requests.request("GET", url, headers=headers, data=payload) + res_json = json.loads(response.text) + return res_json['count'] diff --git a/static/service-worker.js b/static/service-worker.js index ce7f439..ea6e563 100644 --- a/static/service-worker.js +++ b/static/service-worker.js @@ -1,15 +1,15 @@ -self.addEventListener('install', e => { - // console.log('[Service Worker] Installed'); -}); - -self.addEventListener('activate', e => { - // console.log('[Service Worker] Activated'); -}); - -self.addEventListener('fetch', e => { - // e.respondWith( - // caches.match(e.request).then(res => { - // return res || fetch(e.request); - // }) - // ); +self.addEventListener('install', e => { + // console.log('[Service Worker] Installed'); +}); + +self.addEventListener('activate', e => { + // console.log('[Service Worker] Activated'); +}); + +self.addEventListener('fetch', e => { + // e.respondWith( + // caches.match(e.request).then(res => { + // return res || fetch(e.request); + // }) + // ); }); \ No newline at end of file diff --git a/static/styles.css b/static/styles.css index ad5bb13..d401238 100644 --- a/static/styles.css +++ b/static/styles.css @@ -1,30 +1,30 @@ -/* Base styles */ -html, body { - height: 100%; /* Ensure the body and html elements fill the viewport height */ - margin: 0; /* Remove default margin */ - display: flex; - flex-direction: column; -} - -body { - font-family: Arial, sans-serif; -} - -header { - background-color: #4CAF50; - color: white; - padding: 1em; - text-align: center; -} - -main { - flex: 1; /* Make the main content area grow to fill the available space */ - padding: 1em; -} - -footer { - background-color: #333; - color: white; - text-align: center; - padding: 1em; -} +/* Base styles */ +html, body { + height: 100%; /* Ensure the body and html elements fill the viewport height */ + margin: 0; /* Remove default margin */ + display: flex; + flex-direction: column; +} + +body { + font-family: Arial, sans-serif; +} + +header { + background-color: #4CAF50; + color: white; + padding: 1em; + text-align: center; +} + +main { + flex: 1; /* Make the main content area grow to fill the available space */ + padding: 1em; +} + +footer { + background-color: #333; + color: white; + text-align: center; + padding: 1em; +} diff --git a/templates/add_account.html b/templates/add_account.html index 076cbce..0f88dc6 100644 --- a/templates/add_account.html +++ b/templates/add_account.html @@ -1,106 +1,106 @@ - - - - - - - Add Account - KTVManager - - - - - - - - - -
-
- -
-
- - -
-

Add Account

-
-
-
- - -
-
- - -
-
- - -
- -
-
-

Load Details Via OCR

-
-
- - -
- -
- -
- - - - + + + + + + + Add Account - KTVManager + + + + + + + + + +
+
+ +
+
+ + +
+

Add Account

+
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+

Load Details Via OCR

+
+
+ + +
+ +
+ +
+ + + + diff --git a/templates/home.html b/templates/home.html index a3f31ad..223d590 100644 --- a/templates/home.html +++ b/templates/home.html @@ -1,93 +1,93 @@ - - - - - - KTVManager - - - - - - - - - -
-

Welcome {{ username }}!

-
-

You have {{ accounts }} active accounts

-
- - {% if current_month_accounts %} -

Accounts Expiring Within 30 Days

- - - - - - - - - - {% for account in current_month_accounts %} - - - - - - {% endfor %} - -
Stream NameUsernameExpiry Date
{{ account.stream }}{{ account.username }}{{ account.expiaryDate_rendered }}
- {% endif %} - {% if expired_accounts %} -

Expired Accounts

- - - - - - - - - - {% for account in expired_accounts %} - - - - - - {% endfor %} - -
Stream NameUsernameExpiry Date
{{ account.stream }}{{ account.username }}{{ account.expiaryDate_rendered }}
- {% endif %} -
- - - - - - - - - + + + + + + KTVManager + + + + + + + + + +
+

Welcome {{ username }}!

+
+

You have {{ accounts }} active accounts

+
+ + {% if current_month_accounts %} +

Accounts Expiring Within 30 Days

+ + + + + + + + + + {% for account in current_month_accounts %} + + + + + + {% endfor %} + +
Stream NameUsernameExpiry Date
{{ account.stream }}{{ account.username }}{{ account.expiaryDate_rendered }}
+ {% endif %} + {% if expired_accounts %} +

Expired Accounts

+ + + + + + + + + + {% for account in expired_accounts %} + + + + + + {% endfor %} + +
Stream NameUsernameExpiry Date
{{ account.stream }}{{ account.username }}{{ account.expiaryDate_rendered }}
+ {% endif %} +
+ + + + + + + + + diff --git a/templates/index.html b/templates/index.html index bfd3452..f171cc5 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,80 +1,80 @@ - - - - - - - KTVManager - - - - - - - - - - - - - - - -
-

Welcome to KTV Manager

- - -
-
- - -
-
- - -
- - {% if error %} -
{{ error }}
- {% endif %} -
- -
- - - - - - - - - + + + + + + + KTVManager + + + + + + + + + + + + + + + +
+

Welcome to KTV Manager

+ + +
+
+ + +
+
+ + +
+ + {% if error %} +
{{ error }}
+ {% endif %} +
+ +
+ + + + + + + + + diff --git a/templates/urls.html b/templates/urls.html index 9ceb893..f66187c 100644 --- a/templates/urls.html +++ b/templates/urls.html @@ -1,65 +1,65 @@ - - - - - - - KTVManager - - - - - - - - - - -
-

URLs

- - - - - - - - - {% for url in urls %} - - - - - {% endfor %} - -
#URL
{{ loop.index }}{{ url }}
- -
- - - - - - - - + + + + + + + KTVManager + + + + + + + + + + +
+

URLs

+ + + + + + + + + {% for url in urls %} + + + + + {% endfor %} + +
#URL
{{ loop.index }}{{ url }}
+ +
+ + + + + + + + diff --git a/templates/user_accounts.html b/templates/user_accounts.html index 6b67591..919d4b9 100644 --- a/templates/user_accounts.html +++ b/templates/user_accounts.html @@ -1,120 +1,120 @@ - - - - - - KTVManager - - - - - - - - - - -
-
- -
-
- - -
-

{{ username }}'s Accounts

-
- - - - - - - - - - - - - - {% for account in user_accounts %} - - - - - - - - - - {% endfor %} - -
UsernameStreamStream URLExpiry DatePasswordActions
{{ account.username }}{{ account.stream }}{{ account.streamURL }}{{ account.expiaryDate_rendered }}{{ account.password }} -
- - - -
-
-
- -
- - - - - - - - - - - - - + + + + + + KTVManager + + + + + + + + + + +
+
+ +
+
+ + +
+

{{ username }}'s Accounts

+
+ + + + + + + + + + + + + + {% for account in user_accounts %} + + + + + + + + + + {% endfor %} + +
UsernameStreamStream URLExpiry DatePasswordActions
{{ account.username }}{{ account.stream }}{{ account.streamURL }}{{ account.expiaryDate_rendered }}{{ account.password }} +
+ + + +
+
+
+ +
+ + + + + + + + + + + + +