From 63bcbeb9a22dc80d004863b647d203371c5988c4 Mon Sep 17 00:00:00 2001 From: pjpatil12 Date: Wed, 18 Feb 2026 18:00:20 +0530 Subject: [PATCH] modification of code and loggger apply and changes of update from. --- .gitignore | 26 +++----- AppCode/FileHandler.py | 9 ++- AppCode/Log.py | 57 ++++++++++++----- AppCode/LoginAuth.py | 102 ++++++++++++------------------- AppCode/MatCreditHandler.py | 2 +- Dockerfile | 38 ++++++++---- db/income_tax.sql | 0 docker-compose.yml | 37 ++++++++---- main.py | 111 +++++++++++++++++++++++++++++----- requirements.txt | 2 + templates/view_logs.html | 28 +++++++++ templates/view_logs_auth.html | 87 ++++++++++++++++++++++++++ 12 files changed, 361 insertions(+), 138 deletions(-) delete mode 100644 db/income_tax.sql create mode 100644 templates/view_logs.html create mode 100644 templates/view_logs_auth.html diff --git a/.gitignore b/.gitignore index 1fdc339..b3ad797 100644 --- a/.gitignore +++ b/.gitignore @@ -1,30 +1,18 @@ # Python *.__pycache__ *.pyc -*.pyo +*.pyos *.pyd __pycache__ +.vscode/ +.idea/ -# Ingnor upload files +# Ignore upload files static/uploads/ -# Ignore files -venv - -# Ignore Log files ss -logs/ - - - # Environment variables .env +venv -# Python cache -__pycache__/ -*.pyc - -# OS / Editor -.vscode/ -.idea/ -__pycache__/ -*.pyc +# Ignore Log files +logs/ \ No newline at end of file diff --git a/AppCode/FileHandler.py b/AppCode/FileHandler.py index 82ee9b5..9b93a60 100644 --- a/AppCode/FileHandler.py +++ b/AppCode/FileHandler.py @@ -7,7 +7,12 @@ class FileHandler: @staticmethod def CHeckExistingOrCreateNewUploadFolder(): - #Wheteher path exists + # Whether path exists os.makedirs(FileHandler.UPLOAD_FOLDER, exist_ok=True) return - \ No newline at end of file + + @staticmethod + def CheckExistingOrCreateNewLoggerFolder(): + if not os.path.exists("logs"): + os.mkdir("logs") + return \ No newline at end of file diff --git a/AppCode/Log.py b/AppCode/Log.py index 8bc038f..5b4b65d 100644 --- a/AppCode/Log.py +++ b/AppCode/Log.py @@ -1,22 +1,49 @@ import os -from flask import Flask, render_template, request, redirect, url_for, send_from_directory, flash, jsonify, json -from flask import current_app +import logging from datetime import datetime -from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user +from flask import session, request, current_app class LogHelper: + + @staticmethod + def setup_logger(app): + if not os.path.exists("logs"): + os.makedirs("logs") + + formatter = logging.Formatter("%(asctime)s | %(levelname)s | User:%(user)s | IP:%(ip)s | %(message)s") + + file_handler = logging.FileHandler("logs/app.log") + file_handler.setLevel(logging.INFO) + file_handler.setFormatter(formatter) + + stream_handler = logging.StreamHandler() + stream_handler.setLevel(logging.INFO) + stream_handler.setFormatter(formatter) + + app.logger.setLevel(logging.INFO) + app.logger.addHandler(file_handler) + app.logger.addHandler(stream_handler) + + # --------------------------------------- + # Log User Activity + # --------------------------------------- + @staticmethod + def log_request(): + if request.endpoint and "static" not in request.endpoint: + user = session.get("user", "Anonymous") + ip = request.remote_addr + + current_app.logger.info( + f"{request.method} {request.path}", + extra={"user": user, "ip": ip} + ) + + # --------------------------------------- + # Custom Action Logging + # --------------------------------------- @staticmethod def log_action(action, details=""): - """Log user actions with timestamp, user, action, and details.""" - logData = LogData() - logData.WriteLog(action, details="") - - -class LogData: - filepath = "" - timestamp = None - - def __init__(self): - self.filepath = os.path.join(current_app.root_path, 'activity.log') - self.timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + user = session.get("user", "Anonymous") + ip = request.remote_addr + current_app.logger.info(f"{action} | {details}",extra={"user": user, "ip": ip}) diff --git a/AppCode/LoginAuth.py b/AppCode/LoginAuth.py index 263cbf7..793a758 100644 --- a/AppCode/LoginAuth.py +++ b/AppCode/LoginAuth.py @@ -1,4 +1,5 @@ from flask import Blueprint, render_template, request, redirect, url_for, flash, session +import os from functools import wraps from ldap3 import Server, Connection, ALL from ldap3.core.exceptions import LDAPException @@ -9,76 +10,51 @@ class LoginAuth: # Create Blueprint self.bp = Blueprint("auth", __name__) - # ------------------------------- - # LDAP CONFIGURATION - # ------------------------------- - self.LDAP_SERVER = "ldap://localhost:389" - - self.BASE_DN = "ou=users,dc=lcepl,dc=org" # LDAP Users DN + # LDAP CONFIG + self.LDAP_SERVER = os.getenv("LDAP_SERVER", "ldap://host.docker.internal:389") + self.BASE_DN = "ou=users,dc=lcepl,dc=org" - # ------------------------------- - # LOGIN ROUTE - # ------------------------------- - # @self.bp.route('/login', methods=['GET', 'POST']) - # def login(): - # if request.method == 'POST': - # username = request.form.get("username") - # password = request.form.get("password") - # if not username or not password: - # flash("Username and password are required!", "danger") - # return render_template("login.html") - # user_dn = f"uid={username},{self.BASE_DN}" - # server = Server(self.LDAP_SERVER, get_info=ALL) - # try: - # # Attempt LDAP bind - # conn = Connection(server, user=user_dn, password=password, auto_bind=True) - # if conn.bound: - # session['user'] = username - # flash(f"Login successful! Welcome {username}", "success") - # return redirect(url_for('welcome')) - # else: - # flash("Invalid username or password!", "danger") - # except LDAPException as e: - # flash(f"LDAP login failed: {str(e)}", "danger") - # finally: - # if 'conn' in locals(): - # conn.unbind() - # # GET request: show login form - # return render_template("login.html") + # Register Routes + self.bp.add_url_rule("/login", view_func=self.login, methods=["GET", "POST"]) + self.bp.add_url_rule("/logout", view_func=self.logout) + # ================= LOGIN ================= + def login(self): + if request.method == "POST": + username = request.form.get("username") + password = request.form.get("password") - # LOGIN ROUTE - @self.bp.route('/login', methods=['GET', 'POST']) - def login(): - if request.method == 'POST': - username = request.form.get("username") - password = request.form.get("password") - # Dummy validation — REPLACE with DB check later - if username == "admin" and password == "admin123": - session['user'] = username - flash("Login successful!", "success") - return redirect(url_for('welcome')) + if not username or not password: + flash("Username and password are required!", "danger") + return render_template("login.html") + + user_dn = f"uid={username},{self.BASE_DN}" + server = Server(self.LDAP_SERVER, get_info=ALL) + + try: + conn = Connection(server, user=user_dn, password=password, auto_bind=True) + + if conn.bound: + session["user"] = username + flash(f"Login successful! Welcome {username}", "success") + conn.unbind() + return redirect(url_for("welcome")) else: flash("Invalid username or password!", "danger") - return render_template("login.html") - - - # ------------------------------- - # LOGOUT ROUTE - # ------------------------------- - @self.bp.route('/logout') - def logout(): - session.clear() - flash("Logged out successfully!", "success") - return redirect(url_for('auth.login')) - # =================================================== - # LOGIN REQUIRED DECORATOR INSIDE CLASS - # =================================================== + except LDAPException as e: + flash(f"LDAP login failed: {str(e)}", "danger") + + return render_template("login.html") + + # ================= LOGOUT ================= + def logout(self): + session.clear() + flash("Logged out successfully!", "success") + return redirect(url_for("auth.login")) + + # ================= LOGIN REQUIRED ================= def login_required(self, f): - """ - Protect routes: redirect to login if user not authenticated. - """ @wraps(f) def wrapper(*args, **kwargs): if "user" not in session: diff --git a/AppCode/MatCreditHandler.py b/AppCode/MatCreditHandler.py index 360a02f..c4aa753 100644 --- a/AppCode/MatCreditHandler.py +++ b/AppCode/MatCreditHandler.py @@ -13,7 +13,7 @@ class MatCreditHandler: def fetch_all(self): try: - self.cursor.callproc("GetMatCedit") + self.cursor.callproc("GetMatCredit") result_sets = self.cursor.stored_results() mat_rows = next(result_sets).fetchall() utilization_rows = next(result_sets).fetchall() diff --git a/Dockerfile b/Dockerfile index 42dac6e..f8a8dd4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,22 +1,36 @@ +# -------------- development's Dockerfile ---------------- +# FROM python:3.11-slim + +# # Prevent Python buffering +# ENV PYTHONDONTWRITEBYTECODE=1 +# ENV PYTHONUNBUFFERED=1 + +# WORKDIR /app +# # Install system deps (if needed later) +# RUN apt-get update && apt-get install -y \ +# build-essential \ +# && rm -rf /var/lib/apt/lists/* + +# COPY requirements.txt . +# RUN pip install --no-cache-dir -r requirements.txt +# COPY . . +# EXPOSE 5000 +# CMD ["python", "main.py"] + + + + +# -------------- Production Dockerfile ---------------- FROM python:3.11-slim -# Prevent Python buffering -ENV PYTHONDONTWRITEBYTECODE=1 -ENV PYTHONUNBUFFERED=1 - WORKDIR /app -# Install system deps (if needed later) -RUN apt-get update && apt-get install -y \ - build-essential \ - && rm -rf /var/lib/apt/lists/* - COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . -EXPOSE 5000 - -CMD ["python", "main.py"] +EXPOSE 5010 +CMD ["gunicorn", "--bind", "0.0.0.0:5010", "main:app"] +# end \ No newline at end of file diff --git a/db/income_tax.sql b/db/income_tax.sql deleted file mode 100644 index e69de29..0000000 diff --git a/docker-compose.yml b/docker-compose.yml index 62d4f52..ff0d680 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,29 +1,42 @@ version: "3.9" services: + + # Database connection db: - image: mysql:8.0 - container_name: income_tax_db + image: mysql:8 + container_name: tax-mysql restart: always environment: - MYSQL_ROOT_PASSWORD: root - MYSQL_DATABASE: test_income_taxdb - ports: - - "3307:3306" + MYSQL_ROOT_PASSWORD: tiger + MYSQL_DATABASE: income_tax_db volumes: - mysql_data:/var/lib/mysql - - ./db/income_tax.sql:/docker-entrypoint-initdb.d/income_tax.sql - web: + # Application config + flaskapp: build: . - container_name: income_tax_web + container_name: tax-flask restart: always ports: - "5010:5010" - env_file: - - .env depends_on: - db + environment: + DB_HOST: db + DB_PORT: 3306 + DB_USER: root + DB_PASSWORD: tiger + DB_NAME: income_tax_db + FLASK_HOST: 0.0.0.0 + FLASK_PORT: 5010 + FLASK_DEBUG: "false" + SECRET_KEY: secret1234 + LDAP_SERVER: ldap://host.docker.internal:389 + LOG_VIEW_SECRET: super-log-2026 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./logs:/app/logs volumes: - mysql_data: + mysql_data: \ No newline at end of file diff --git a/main.py b/main.py index 2d3b0cd..4903288 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,6 @@ -from flask import Flask, render_template, request, redirect, url_for, flash,send_file ,jsonify +from flask import Flask, render_template, request, redirect, url_for, flash,send_file ,jsonify, session import os from dotenv import load_dotenv -load_dotenv() from werkzeug.utils import secure_filename from datetime import date from AppCode.Config import DBConfig @@ -14,7 +13,12 @@ from AppCode.AOHandler import AOHandler from AppCode.CITHandler import CITHandler from AppCode.ITATHandler import ITATHandler from AppCode.MatCreditHandler import MatCreditHandler +import logging +import sys +from AppCode.Log import LogHelper +# Loading env file +load_dotenv() # Server app = Flask(__name__) @@ -24,6 +28,13 @@ app.secret_key=os.getenv("SECRET_KEY") auth = LoginAuth() app.register_blueprint(auth.bp) +# LOGGING SETUP +LogHelper.setup_logger(app) + +@app.before_request +def log_all_requests(): + LogHelper.log_request() + # welcome page @app.route('/') @@ -46,6 +57,7 @@ def upload_file(): FileHandler.CHeckExistingOrCreateNewUploadFolder() docHandler = DocumentHandler() docHandler.Upload(request=request) + LogHelper.log_action("UPLOAD", "Document uploaded") return redirect(url_for('view_documents')) return render_template('upload.html') @@ -68,7 +80,7 @@ def uploaded_file(filename): if not os.path.exists(filepath): flash("Unsupported file type for viewing", "warning") return redirect(url_for('view_documents')) - + LogHelper.log_action("VIEW FILE", filename) file_ext = filename.rsplit('.', 1)[-1].lower() # --- View Mode --- if mode == 'view': @@ -124,7 +136,7 @@ def add_itr(): mat_utilized=float(request.form.get("mat_credit_utilized", 0)), remarks="Created via ITR" ) - + LogHelper.log_action("ADD ITR Record", f"Year: {request.form['year']}") # flash("ITR record added successfully!", "success") flash("ITR record and documents uploaded successfully!", "success") return redirect(url_for('display_itr')) @@ -138,6 +150,7 @@ def delete_itr(id): itr = ITRHandler() itr.delete_itr_by_id(id=id) itr.close() + LogHelper.log_action("ITR record deleted successfully!", id) return redirect(url_for('display_itr')) ## 3. UPDATE an existing ITR record @@ -150,8 +163,19 @@ def update_itr(id): data = {k: request.form.get(k, 0) for k in request.form} itr.update(id, data) itr.close() - return redirect(url_for('display_itr')) + mat = MatCreditHandler() + # AUTO SAVE MAT FROM ITR + mat.save_from_itr( + year=request.form["year"], + mat_created=float(request.form.get("mat_credit_created", 0)), + opening_balance=float(request.form.get("opening_balance", 0)), + mat_utilized=float(request.form.get("mat_credit_utilized", 0)), + remarks="Updated via ITR" + ) + LogHelper.log_action("ITR record updated successfully!", data) + return redirect(url_for('display_itr')) + record = itr.get_itr_by_id(id) itr.close() return render_template('update_itr.html', record=record, current_date=date.today().isoformat()) @@ -193,7 +217,7 @@ def add_ao(): mat_utilized=float(request.form.get("mat_credit_utilized", 0)), remarks="Created via ao" ) - + LogHelper.log_action("AO record added successfully!", "") flash("AO record added successfully!", "success") return redirect(url_for('display_ao')) return render_template('add_ao.html',current_date=date.today().isoformat()) @@ -212,6 +236,15 @@ def update_ao(id): data = request.form.to_dict() ao.update_ao(id, data) ao.close() + mat = MatCreditHandler() + mat.save_from_itr( + year=request.form["year"], + mat_created=float(request.form.get("mat_credit_created", 0)), + opening_balance=float(request.form.get("opening_balance", 0)), + mat_utilized=float(request.form.get("mat_credit_utilized", 0)), + remarks="Created via ao" + ) + LogHelper.log_action("AO record updated successfully!", data) flash("AO record updated successfully!", "success") return redirect(url_for('display_ao')) @@ -226,6 +259,7 @@ def delete_ao(id): ao = AOHandler() ao.delete_ao_by_id(id=id) ao.close() + LogHelper.log_action("AO deleted successfully!", id) flash("AO deleted successfully!", "success") return redirect(url_for('display_ao')) @@ -266,6 +300,7 @@ def add_cit(): mat_utilized=float(request.form.get("mat_credit_utilized", 0)), remarks="Created via cit" ) + LogHelper.log_action("CIT record added successfully!", "") flash("CIT record added successfully!", "success") return redirect(url_for('display_cit')) @@ -278,6 +313,7 @@ def delete_cit(id): cit = CITHandler() cit.delete_cit(id) cit.close() + LogHelper.log_action("CIT record deleted successfully!", id) flash("CIT record deleted successfully!", "success") return redirect(url_for('display_cit')) @@ -296,6 +332,16 @@ def update_cit(id): data = {k: request.form.get(k, 0) for k in request.form} cit.update_cit(id, data) cit.close() + mat = MatCreditHandler() + # AUTO SAVE MAT FROM ITR + mat.save_from_itr( + year=request.form["year"], + mat_created=float(request.form.get("mat_credit_created", 0)), + opening_balance=float(request.form.get("opening_balance", 0)), + mat_utilized=float(request.form.get("mat_credit_utilized", 0)), + remarks="Updated via cit" + ) + LogHelper.log_action("CIT record updated successfully!", data) return redirect(url_for('display_cit')) cit.close() @@ -336,9 +382,9 @@ def add_itat(): mat_created=float(request.form.get("mat_credit_created", 0)), opening_balance=float(request.form.get("opening_balance", 0)), mat_utilized=float(request.form.get("mat_credit_utilized", 0)), - remarks="Created via ITR" + remarks="Created via ITAT" ) - + LogHelper.log_action("ITAT record added successfully!", data) flash("ITAT record added successfully!", "success") return redirect(url_for('display_itat')) @@ -358,6 +404,16 @@ def update_itat(id): if request.method == 'POST': itat.update_itat(id, request.form) itat.close() + mat = MatCreditHandler() + mat.save_from_itr( + year=request.form["year"], + mat_created=float(request.form.get("mat_credit_created", 0)), + opening_balance=float(request.form.get("opening_balance", 0)), + mat_utilized=float(request.form.get("mat_credit_utilized", 0)), + remarks="Updated via ITAT" + ) + + LogHelper.log_action("ITAT Record Updated!", id) flash("ITAT Record Updated!", "success") return redirect(url_for('display_itat')) @@ -370,6 +426,7 @@ def update_itat(id): def delete_itat(id): itat = ITATHandler() itat.delete_itat_by_id(id) + LogHelper.log_action("itat record of by id:", id) itat.close() flash("ITAT Record Deleted!", "success") return redirect(url_for('display_itat')) @@ -398,7 +455,7 @@ def itr_report(): if output is None: return "No records found for the selected year." - + LogHelper.log_action("itr report download", selected_year) return send_file( output, mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', @@ -425,7 +482,7 @@ def ao_report(): if output is None: return "No records found for the selected year." - + LogHelper.log_action("ao report download", selected_year) return send_file( output, mimetype="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", @@ -453,7 +510,7 @@ def cit_report(): if output is None: return "No records found for the selected year." - + LogHelper.log_action("cit report download", selected_year) return send_file( output, mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', @@ -482,7 +539,7 @@ def itat_report(): if output is None: return "No records found for the selected year." - + LogHelper.log_action("itat report download", selected_year) return send_file( output, mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', @@ -512,7 +569,7 @@ def download_summary(): return "Year parameter is required", 400 docHandler = DocumentHandler() - # reuse your existing Summary_report method + LogHelper.log_action("/summary/download | download summary sheet !",year_raw) return docHandler.Summary_report(request=request) @@ -562,8 +619,9 @@ def mat_credit(): for u in utilization_rows: all_years.add(u["utilized_year"]) utilization_map.setdefault( - u["mat_credit_id"], {} + u["mat_credit_id"], {} )[u["utilized_year"]] = u["utilized_amount"] + LogHelper.log_action("/mat_credit| Save mat credit !",all_years) return render_template( "mat_credit.html", @@ -579,6 +637,7 @@ def save_mat_row(): mat = MatCreditHandler() try: mat.save_single(request.json) + LogHelper.log_action("/save_mat_row", "Save Mat row!") return jsonify({"message": "Row saved successfully"}) except Exception as e: return jsonify({"error": str(e)}), 500 @@ -602,6 +661,30 @@ def summary_preview_route(): # except Exception as e: # return jsonify({"error": str(e)}), 500 + +@app.route("/view_logs", methods=["GET", "POST"]) +@auth.login_required +def view_logs(): + secret = os.getenv("LOG_VIEW_SECRET") + + if request.method == "POST": + entered = request.form.get("secret") + + if entered != secret: + flash("Invalid secret!", "danger") + return render_template("view_logs_auth.html") + try: + with open("logs/app.log", "r") as f: + logs = f.readlines() + except FileNotFoundError: + logs = ["Log file not found"] + + return render_template("view_logs.html", logs=logs) + + return render_template("view_logs_auth.html") + + + # run server if __name__ == '__main__': app.run( diff --git a/requirements.txt b/requirements.txt index 6196a9f..c311d1b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,3 +13,5 @@ xlrd==2.0.1 gunicorn==21.2.0 XlsxWriter==3.2.0 + +ldap3 \ No newline at end of file diff --git a/templates/view_logs.html b/templates/view_logs.html new file mode 100644 index 0000000..8226b7d --- /dev/null +++ b/templates/view_logs.html @@ -0,0 +1,28 @@ + + + + + + + Document + + + + +

Application Logs

+
{% for line in logs %} {{ line }} {% endfor %}
+ + + \ No newline at end of file diff --git a/templates/view_logs_auth.html b/templates/view_logs_auth.html new file mode 100644 index 0000000..c8f6dd5 --- /dev/null +++ b/templates/view_logs_auth.html @@ -0,0 +1,87 @@ + + + + + + + View Logs - Authorization + + + + +
+

Enter Secret to View Logs

+
+ + +
+ {% with messages = get_flashed_messages(with_categories=true) %} + {% for category, message in messages %} +
{{ message }}
+ {% endfor %} + {% endwith %} +
+ + + \ No newline at end of file