Compare commits
2 Commits
568428b5d0
...
5ab11bc5a6
| Author | SHA1 | Date | |
|---|---|---|---|
| 5ab11bc5a6 | |||
| 45bfd4b592 |
@@ -1,6 +1,7 @@
|
|||||||
from flask import Flask, redirect, url_for
|
from flask import Flask, redirect, url_for
|
||||||
from app.config import Config
|
from app.config import Config
|
||||||
from app.services.db_service import db
|
from app.services.db_service import db
|
||||||
|
from app.services.logger_service import LoggerService
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
@@ -9,6 +10,9 @@ def create_app():
|
|||||||
# Initialize extensions
|
# Initialize extensions
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
|
|
||||||
|
# Initialize Logger
|
||||||
|
LoggerService.init_app(app)
|
||||||
|
|
||||||
# Register blueprints
|
# Register blueprints
|
||||||
register_blueprints(app)
|
register_blueprints(app)
|
||||||
# Register error handlers
|
# Register error handlers
|
||||||
@@ -43,10 +47,15 @@ def register_blueprints(app):
|
|||||||
|
|
||||||
|
|
||||||
def register_error_handlers(app):
|
def register_error_handlers(app):
|
||||||
|
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
def page_not_found(e):
|
def page_not_found(e):
|
||||||
|
current_app.logger.warning("404 Page Not Found")
|
||||||
return "Page Not Found", 404
|
return "Page Not Found", 404
|
||||||
|
|
||||||
@app.errorhandler(500)
|
@app.errorhandler(500)
|
||||||
def internal_error(e):
|
def internal_error(e):
|
||||||
|
current_app.logger.exception("500 Internal Server Error")
|
||||||
return "Internal Server Error", 500
|
return "Internal Server Error", 500
|
||||||
11
app/constants/http_status.py
Normal file
11
app/constants/http_status.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
class HTTPStatus:
|
||||||
|
OK = 200
|
||||||
|
CREATED = 201
|
||||||
|
BAD_REQUEST = 400
|
||||||
|
UNAUTHORIZED = 401
|
||||||
|
FORBIDDEN = 403
|
||||||
|
NOT_FOUND = 404
|
||||||
|
METHOD_NOT_ALLOWED = 405
|
||||||
|
CONFLICT = 409
|
||||||
|
UNPROCESSABLE_ENTITY = 422
|
||||||
|
INTERNAL_SERVER_ERROR = 500
|
||||||
17
app/constants/messages.py
Normal file
17
app/constants/messages.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
class SuccessMessage:
|
||||||
|
FETCHED = "Data fetched successfully"
|
||||||
|
CREATED = "Resource created successfully"
|
||||||
|
UPDATED = "Resource updated successfully"
|
||||||
|
DELETED = "Resource deleted successfully"
|
||||||
|
LOGIN = "Login successful"
|
||||||
|
LOGOUT = "Logout successful"
|
||||||
|
|
||||||
|
|
||||||
|
class ErrorMessage:
|
||||||
|
INVALID_REQUEST = "Invalid request data"
|
||||||
|
UNAUTHORIZED = "Unauthorized access"
|
||||||
|
FORBIDDEN = "Access forbidden"
|
||||||
|
NOT_FOUND = "Resource not found"
|
||||||
|
VALIDATION_FAILED = "Validation failed"
|
||||||
|
INTERNAL_ERROR = "Internal server error"
|
||||||
|
DUPLICATE_ENTRY = "Duplicate record found"
|
||||||
66
app/errors/error_handlers.py
Normal file
66
app/errors/error_handlers.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
from flask import request, render_template
|
||||||
|
from app.utils.response_handler import ResponseHandler
|
||||||
|
from app.utils.exceptions import APIException
|
||||||
|
from app.constants.http_status import HTTPStatus
|
||||||
|
from app.constants.messages import ErrorMessage
|
||||||
|
from app.services.db_service import db
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
|
def register_error_handlers(app):
|
||||||
|
|
||||||
|
# Custom API Exception
|
||||||
|
@app.errorhandler(APIException)
|
||||||
|
def handle_api_exception(e):
|
||||||
|
db.session.rollback()
|
||||||
|
|
||||||
|
if request.path.startswith("/api"):
|
||||||
|
return ResponseHandler.error(
|
||||||
|
message=e.message,
|
||||||
|
errors=e.errors,
|
||||||
|
status_code=e.status_code
|
||||||
|
)
|
||||||
|
|
||||||
|
return render_template("errors/500.html"), e.status_code
|
||||||
|
|
||||||
|
|
||||||
|
# 404
|
||||||
|
@app.errorhandler(404)
|
||||||
|
def handle_404(e):
|
||||||
|
if request.path.startswith("/api"):
|
||||||
|
return ResponseHandler.error(
|
||||||
|
message=ErrorMessage.NOT_FOUND,
|
||||||
|
status_code=HTTPStatus.NOT_FOUND
|
||||||
|
)
|
||||||
|
|
||||||
|
return render_template("errors/404.html"), 404
|
||||||
|
|
||||||
|
|
||||||
|
# 500
|
||||||
|
@app.errorhandler(500)
|
||||||
|
def handle_500(e):
|
||||||
|
db.session.rollback()
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
if request.path.startswith("/api"):
|
||||||
|
return ResponseHandler.error(
|
||||||
|
message=ErrorMessage.INTERNAL_ERROR,
|
||||||
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
return render_template("errors/500.html"), 500
|
||||||
|
|
||||||
|
|
||||||
|
# Catch All
|
||||||
|
@app.errorhandler(Exception)
|
||||||
|
def handle_general_exception(e):
|
||||||
|
db.session.rollback()
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
if request.path.startswith("/api"):
|
||||||
|
return ResponseHandler.error(
|
||||||
|
message=ErrorMessage.INTERNAL_ERROR,
|
||||||
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
return render_template("errors/500.html"), 500
|
||||||
@@ -16,7 +16,11 @@ from app.models.laying_model import Laying
|
|||||||
dashboard_bp = Blueprint("dashboard", __name__, url_prefix="/dashboard")
|
dashboard_bp = Blueprint("dashboard", __name__, url_prefix="/dashboard")
|
||||||
|
|
||||||
|
|
||||||
|
@dashboard_bp.route("/")
|
||||||
|
def dashboard():
|
||||||
|
if not session.get("user_id"):
|
||||||
|
return redirect(url_for("auth.login"))
|
||||||
|
return render_template("dashboard.html", title="Business Intelligence Dashboard")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -144,11 +148,6 @@ def live_stats():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({"error": str(e)}), 500
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
@dashboard_bp.route("/")
|
|
||||||
def dashboard():
|
|
||||||
if not session.get("user_id"):
|
|
||||||
return redirect(url_for("auth.login"))
|
|
||||||
return render_template("dashboard.html", title="Business Intelligence Dashboard")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,38 @@
|
|||||||
from flask import Blueprint, render_template, request, redirect, flash
|
from flask import Blueprint, render_template, request, redirect, flash, current_app, url_for
|
||||||
from app import db
|
from app.services.db_service import db
|
||||||
from app.models.subcontractor_model import Subcontractor
|
from app.models.subcontractor_model import Subcontractor
|
||||||
from app.utils.helpers import login_required
|
from app.utils.helpers import login_required
|
||||||
|
|
||||||
subcontractor_bp = Blueprint("subcontractor", __name__, url_prefix="/subcontractor")
|
subcontractor_bp = Blueprint("subcontractor", __name__, url_prefix="/subcontractor")
|
||||||
|
|
||||||
|
|
||||||
# ---------------- ADD -----------------
|
# ---------------- ADD -----------------
|
||||||
@subcontractor_bp.route("/add")
|
@subcontractor_bp.route("/add")
|
||||||
@login_required
|
@login_required
|
||||||
def add_subcontractor():
|
def add_subcontractor():
|
||||||
|
current_app.logger.info("Opened Add Subcontractor Page")
|
||||||
return render_template("subcontractor/add.html")
|
return render_template("subcontractor/add.html")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------- SAVE -----------------
|
||||||
@subcontractor_bp.route("/save", methods=["POST"])
|
@subcontractor_bp.route("/save", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def save_subcontractor():
|
def save_subcontractor():
|
||||||
# 1. Get and clean the name from the form
|
|
||||||
name = request.form.get("subcontractor_name", "").strip()
|
name = request.form.get("subcontractor_name", "").strip()
|
||||||
|
|
||||||
# 2. Basic validation: Ensure the name isn't empty
|
|
||||||
if not name:
|
if not name:
|
||||||
|
current_app.logger.warning("Empty subcontractor name submitted")
|
||||||
flash("Subcontractor name cannot be empty.", "danger")
|
flash("Subcontractor name cannot be empty.", "danger")
|
||||||
return redirect("/subcontractor/add")
|
return redirect(url_for("subcontractor.add_subcontractor"))
|
||||||
|
|
||||||
# 3. Check if a subcontractor with this name already exists
|
|
||||||
existing_sub = Subcontractor.query.filter_by(subcontractor_name=name).first()
|
existing_sub = Subcontractor.query.filter_by(subcontractor_name=name).first()
|
||||||
|
|
||||||
if existing_sub:
|
if existing_sub:
|
||||||
|
current_app.logger.warning(f"Duplicate subcontractor attempt: {name}")
|
||||||
flash(f"Subcontractor with name '{name}' already exists!", "danger")
|
flash(f"Subcontractor with name '{name}' already exists!", "danger")
|
||||||
return redirect("/subcontractor/add")
|
return redirect(url_for("subcontractor.add_subcontractor"))
|
||||||
|
|
||||||
# 4. If no duplicate is found, proceed to save
|
|
||||||
try:
|
try:
|
||||||
subcontractor = Subcontractor(
|
subcontractor = Subcontractor(
|
||||||
subcontractor_name=name,
|
subcontractor_name=name,
|
||||||
@@ -40,143 +44,93 @@ def save_subcontractor():
|
|||||||
|
|
||||||
db.session.add(subcontractor)
|
db.session.add(subcontractor)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
current_app.logger.info(f"Subcontractor Created Successfully: {name}")
|
||||||
flash("Subcontractor added successfully!", "success")
|
flash("Subcontractor added successfully!", "success")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
flash("An error occurred while saving. Please try again.", "danger")
|
current_app.logger.exception("Error while saving subcontractor")
|
||||||
|
flash("An error occurred while saving.", "danger")
|
||||||
|
|
||||||
return redirect("/subcontractor/list")
|
return redirect(url_for("subcontractor.subcontractor_list"))
|
||||||
|
|
||||||
# ---------------- LIST -----------------
|
|
||||||
|
# ---------------- LIST (UPDATED WITH PAGINATION) -----------------
|
||||||
@subcontractor_bp.route("/list")
|
@subcontractor_bp.route("/list")
|
||||||
@login_required
|
@login_required
|
||||||
def subcontractor_list():
|
def subcontractor_list():
|
||||||
subcontractors = Subcontractor.query.all()
|
|
||||||
return render_template("subcontractor/list.html", subcontractors=subcontractors)
|
page = request.args.get("page", 1, type=int)
|
||||||
|
per_page = 10 # Change how many records per page
|
||||||
|
|
||||||
|
pagination = Subcontractor.query.order_by(
|
||||||
|
Subcontractor.created_at
|
||||||
|
).paginate(
|
||||||
|
page=page,
|
||||||
|
per_page=per_page,
|
||||||
|
error_out=False
|
||||||
|
)
|
||||||
|
|
||||||
|
subcontractors = pagination.items
|
||||||
|
|
||||||
|
current_app.logger.info(f"Viewed Subcontractor List - Page {page}")
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"subcontractor/list.html",
|
||||||
|
subcontractors=subcontractors,
|
||||||
|
pagination=pagination
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# ---------------- EDIT -----------------
|
# ---------------- EDIT -----------------
|
||||||
@subcontractor_bp.route("/edit/<int:id>")
|
@subcontractor_bp.route("/edit/<int:id>")
|
||||||
@login_required
|
@login_required
|
||||||
def edit_subcontractor(id):
|
def edit_subcontractor(id):
|
||||||
subcontractor = Subcontractor.query.get_or_404(id)
|
subcontractor = Subcontractor.query.get_or_404(id)
|
||||||
|
current_app.logger.info(f"Editing Subcontractor ID: {id}")
|
||||||
return render_template("subcontractor/edit.html", subcontractor=subcontractor)
|
return render_template("subcontractor/edit.html", subcontractor=subcontractor)
|
||||||
|
|
||||||
|
|
||||||
# ---------------- UPDATE -----------------
|
# ---------------- UPDATE -----------------
|
||||||
@subcontractor_bp.route("/update/<int:id>", methods=["POST"])
|
@subcontractor_bp.route("/update/<int:id>", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def update_subcontractor(id):
|
def update_subcontractor(id):
|
||||||
subcontractor = Subcontractor.query.get_or_404(id)
|
|
||||||
new_name = request.form.get("subcontractor_name")
|
|
||||||
|
|
||||||
# Check if the new name is taken by someone ELSE (not this current ID)
|
subcontractor = Subcontractor.query.get_or_404(id)
|
||||||
|
new_name = request.form.get("subcontractor_name", "").strip()
|
||||||
|
|
||||||
duplicate = Subcontractor.query.filter(
|
duplicate = Subcontractor.query.filter(
|
||||||
Subcontractor.subcontractor_name == new_name,
|
Subcontractor.subcontractor_name == new_name,
|
||||||
Subcontractor.id != id
|
Subcontractor.id != id
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if duplicate:
|
if duplicate:
|
||||||
|
current_app.logger.warning(f"Duplicate update attempt: {new_name}")
|
||||||
flash("Another subcontractor already uses this name.", "danger")
|
flash("Another subcontractor already uses this name.", "danger")
|
||||||
return redirect(f"/subcontractor/edit/{id}")
|
return redirect(url_for("subcontractor.edit_subcontractor", id=id))
|
||||||
|
|
||||||
subcontractor.subcontractor_name = new_name
|
|
||||||
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
flash("Subcontractor updated successfully!", "success")
|
|
||||||
return redirect("/subcontractor/list")
|
|
||||||
|
|
||||||
# ---------------- DELETE -----------------
|
|
||||||
@subcontractor_bp.route("/delete/<int:id>")
|
|
||||||
@login_required
|
|
||||||
def delete_subcontractor(id):
|
|
||||||
subcontractor = Subcontractor.query.get_or_404(id)
|
|
||||||
|
|
||||||
db.session.delete(subcontractor)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
flash("Subcontractor deleted successfully!", "success")
|
|
||||||
return redirect("/subcontractor/list")
|
|
||||||
from flask import Blueprint, render_template, request, redirect, flash
|
|
||||||
from app import db
|
|
||||||
from app.models.subcontractor_model import Subcontractor
|
|
||||||
from app.utils.helpers import login_required
|
|
||||||
|
|
||||||
subcontractor_bp = Blueprint("subcontractor", __name__, url_prefix="/subcontractor")
|
|
||||||
|
|
||||||
# ---------------- ADD -----------------
|
|
||||||
@subcontractor_bp.route("/add")
|
|
||||||
@login_required
|
|
||||||
def add_subcontractor():
|
|
||||||
return render_template("subcontractor/add.html")
|
|
||||||
|
|
||||||
@subcontractor_bp.route("/save", methods=["POST"])
|
|
||||||
@login_required
|
|
||||||
def save_subcontractor():
|
|
||||||
name = request.form.get("subcontractor_name", "").strip()
|
|
||||||
if not name:
|
|
||||||
flash("Subcontractor name cannot be empty.", "danger")
|
|
||||||
return redirect("/subcontractor/add")
|
|
||||||
existing_sub = Subcontractor.query.filter_by(subcontractor_name=name).first()
|
|
||||||
|
|
||||||
if existing_sub:
|
|
||||||
flash(f"Subcontractor with name '{name}' already exists!", "danger")
|
|
||||||
return redirect("/subcontractor/add")
|
|
||||||
try:
|
try:
|
||||||
subcontractor = Subcontractor(
|
old_name = subcontractor.subcontractor_name
|
||||||
subcontractor_name=name,
|
|
||||||
contact_person=request.form.get("contact_person"),
|
subcontractor.subcontractor_name = new_name
|
||||||
mobile_no=request.form.get("mobile_no"),
|
subcontractor.contact_person = request.form.get("contact_person")
|
||||||
email_id=request.form.get("email_id"),
|
subcontractor.mobile_no = request.form.get("mobile_no")
|
||||||
gst_no=request.form.get("gst_no")
|
subcontractor.email_id = request.form.get("email_id")
|
||||||
)
|
subcontractor.gst_no = request.form.get("gst_no")
|
||||||
|
|
||||||
db.session.add(subcontractor)
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash("Subcontractor added successfully!", "success")
|
|
||||||
|
|
||||||
except Exception as e:
|
current_app.logger.info(f"Subcontractor Updated: {old_name} → {new_name}")
|
||||||
|
flash("Subcontractor updated successfully!", "success")
|
||||||
|
|
||||||
|
except Exception:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
flash("An error occurred while saving. Please try again.", "danger")
|
current_app.logger.exception("Error updating subcontractor")
|
||||||
|
flash("Update failed!", "danger")
|
||||||
|
|
||||||
return redirect("/subcontractor/list")
|
return redirect(url_for("subcontractor.subcontractor_list"))
|
||||||
|
|
||||||
# ---------------- LIST -----------------
|
|
||||||
@subcontractor_bp.route("/list")
|
|
||||||
@login_required
|
|
||||||
def subcontractor_list():
|
|
||||||
subcontractors = Subcontractor.query.all()
|
|
||||||
return render_template("subcontractor/list.html", subcontractors=subcontractors)
|
|
||||||
|
|
||||||
# ---------------- EDIT -----------------
|
|
||||||
@subcontractor_bp.route("/edit/<int:id>")
|
|
||||||
@login_required
|
|
||||||
def edit_subcontractor(id):
|
|
||||||
subcontractor = Subcontractor.query.get_or_404(id)
|
|
||||||
return render_template("subcontractor/edit.html", subcontractor=subcontractor)
|
|
||||||
|
|
||||||
# ---------------- UPDATE -----------------
|
|
||||||
@subcontractor_bp.route("/update/<int:id>", methods=["POST"])
|
|
||||||
@login_required
|
|
||||||
def update_subcontractor(id):
|
|
||||||
subcontractor = Subcontractor.query.get_or_404(id)
|
|
||||||
new_name = request.form.get("subcontractor_name")
|
|
||||||
|
|
||||||
# Check if the new name is taken by someone ELSE (not this current ID)
|
|
||||||
duplicate = Subcontractor.query.filter(
|
|
||||||
Subcontractor.subcontractor_name == new_name,
|
|
||||||
Subcontractor.id != id
|
|
||||||
).first()
|
|
||||||
|
|
||||||
if duplicate:
|
|
||||||
flash("Another subcontractor already uses this name.", "danger")
|
|
||||||
return redirect(f"/subcontractor/edit/{id}")
|
|
||||||
|
|
||||||
subcontractor.subcontractor_name = new_name
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
flash("Subcontractor updated successfully!", "success")
|
|
||||||
return redirect("/subcontractor/list")
|
|
||||||
|
|
||||||
# ---------------- DELETE -----------------
|
# ---------------- DELETE -----------------
|
||||||
@subcontractor_bp.route("/delete/<int:id>")
|
@subcontractor_bp.route("/delete/<int:id>")
|
||||||
@@ -184,8 +138,17 @@ def update_subcontractor(id):
|
|||||||
def delete_subcontractor(id):
|
def delete_subcontractor(id):
|
||||||
subcontractor = Subcontractor.query.get_or_404(id)
|
subcontractor = Subcontractor.query.get_or_404(id)
|
||||||
|
|
||||||
db.session.delete(subcontractor)
|
try:
|
||||||
db.session.commit()
|
name = subcontractor.subcontractor_name
|
||||||
|
db.session.delete(subcontractor)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
flash("Subcontractor deleted successfully!", "success")
|
current_app.logger.info(f"Subcontractor Deleted: {name}")
|
||||||
return redirect("/subcontractor/list")
|
flash("Subcontractor deleted successfully!", "success")
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
db.session.rollback()
|
||||||
|
current_app.logger.exception("Error deleting subcontractor")
|
||||||
|
flash("Delete failed!", "danger")
|
||||||
|
|
||||||
|
return redirect(url_for("subcontractor.subcontractor_list"))
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
from flask import Blueprint, render_template
|
from flask import Blueprint, render_template
|
||||||
from app.services.user_service import UserService
|
from app.services.user_service import UserService
|
||||||
from app.utils.helpers import login_required
|
from app.utils.helpers import login_required
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
user_bp = Blueprint("user", __name__, url_prefix="/user")
|
user_bp = Blueprint("user", __name__, url_prefix="/user")
|
||||||
|
|
||||||
@user_bp.route("/list")
|
@user_bp.route("/list")
|
||||||
@login_required
|
@login_required
|
||||||
def list_users():
|
def list_users():
|
||||||
|
current_app.logger.info("User list viewed")
|
||||||
users = UserService.get_all_users()
|
users = UserService.get_all_users()
|
||||||
return render_template("users.html", users=users, title="Users")
|
return render_template("users.html", users=users, title="Users")
|
||||||
0
app/services/comparison_service.py
Normal file
0
app/services/comparison_service.py
Normal file
82
app/services/logger_service.py
Normal file
82
app/services/logger_service.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
from flask import request, session
|
||||||
|
|
||||||
|
|
||||||
|
class RequestFormatter(logging.Formatter):
|
||||||
|
"""
|
||||||
|
Custom formatter to safely inject request data
|
||||||
|
"""
|
||||||
|
|
||||||
|
def format(self, record):
|
||||||
|
record.user = getattr(record, "user", "Anonymous")
|
||||||
|
record.ip = getattr(record, "ip", "N/A")
|
||||||
|
record.method = getattr(record, "method", "N/A")
|
||||||
|
record.url = getattr(record, "url", "N/A")
|
||||||
|
return super().format(record)
|
||||||
|
|
||||||
|
|
||||||
|
class LoggerService:
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def init_app(app):
|
||||||
|
|
||||||
|
# Create logs folder if not exists
|
||||||
|
if not os.path.exists("logs"):
|
||||||
|
os.makedirs("logs")
|
||||||
|
|
||||||
|
formatter = RequestFormatter(
|
||||||
|
"%(asctime)s | %(levelname)s | "
|
||||||
|
"User:%(user)s | IP:%(ip)s | "
|
||||||
|
"Method:%(method)s | URL:%(url)s | "
|
||||||
|
"%(message)s"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 🔹 INFO LOG
|
||||||
|
info_handler = RotatingFileHandler(
|
||||||
|
"logs/app.log",
|
||||||
|
maxBytes=5 * 1024 * 1024,
|
||||||
|
backupCount=5
|
||||||
|
)
|
||||||
|
info_handler.setLevel(logging.INFO)
|
||||||
|
info_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
# 🔹 ERROR LOG
|
||||||
|
error_handler = RotatingFileHandler(
|
||||||
|
"logs/error.log",
|
||||||
|
maxBytes=5 * 1024 * 1024,
|
||||||
|
backupCount=5
|
||||||
|
)
|
||||||
|
error_handler.setLevel(logging.ERROR)
|
||||||
|
error_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
# 🔹 CONSOLE LOG
|
||||||
|
console_handler = logging.StreamHandler()
|
||||||
|
console_handler.setLevel(logging.DEBUG)
|
||||||
|
console_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
app.logger.setLevel(logging.DEBUG)
|
||||||
|
app.logger.addHandler(info_handler)
|
||||||
|
app.logger.addHandler(error_handler)
|
||||||
|
app.logger.addHandler(console_handler)
|
||||||
|
|
||||||
|
# Auto request logging
|
||||||
|
@app.before_request
|
||||||
|
def log_request():
|
||||||
|
app.logger.info(
|
||||||
|
"Request Started",
|
||||||
|
extra=LoggerService.get_request_data()
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_request_data():
|
||||||
|
try:
|
||||||
|
return {
|
||||||
|
"user": session.get("user_email", "Anonymous"),
|
||||||
|
"ip": request.remote_addr,
|
||||||
|
"method": request.method,
|
||||||
|
"url": request.url
|
||||||
|
}
|
||||||
|
except:
|
||||||
|
return {}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid px-2 px-md-4">
|
<div class="container-fluid px-2 px-md-4">
|
||||||
<h4 class="mb-3 text-center text-md-start">Comparison dSoftware Solapur (UGD) - Live Dashboard</h4>
|
<h4 class="mb-3 text-center text-md-start">Comparison Software Solapur (UGD) - Live Dashboard</h4>
|
||||||
|
|
||||||
<div class="row g-3 mb-4">
|
<div class="row g-3 mb-4">
|
||||||
<div class="col-12 col-md-4">
|
<div class="col-12 col-md-4">
|
||||||
|
|||||||
@@ -2,39 +2,39 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="card shadow-sm p-4">
|
<div class="card shadow-sm p-4">
|
||||||
|
|
||||||
<h4 class="mb-3">Add New Subcontractor</h4>
|
<h4 class="mb-3">Add New Subcontractor</h4>
|
||||||
|
|
||||||
<form action="/subcontractor/save" method="POST">
|
<form action="{{ url_for('subcontractor.save_subcontractor') }}" method="POST">
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Subcontractor Name</label>
|
<label class="form-label">Subcontractor Name:</label>
|
||||||
<input type="text" class="form-control" name="subcontractor_name" required>
|
<input type="text" class="form-control" name="subcontractor_name" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Contact Person Name</label>
|
<label class="form-label">Contact Person Name:</label>
|
||||||
<input type="text" class="form-control" name="contact_person">
|
<input type="text" class="form-control" name="contact_person">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Mobile</label>
|
<label class="form-label">Mobile No:</label>
|
||||||
<input type="text" class="form-control" name="mobile_no">
|
<input type="text" class="form-control" name="mobile_no">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Email</label>
|
<label class="form-label">Email:</label>
|
||||||
<input type="email" class="form-control" name="email_id">
|
<input type="email" class="form-control" name="email_id">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">GST No</label>
|
<label class="form-label">GST No:</label>
|
||||||
<input type="text" class="form-control" name="gst_no">
|
<input type="text" class="form-control" name="gst_no">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="btn btn-success">Save</button>
|
<button class="btn btn-success">Save</button>
|
||||||
</form>
|
<a href="{{ url_for('subcontractor.subcontractor_list') }}" class="btn btn-secondary">Back</a>
|
||||||
|
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<div class="card shadow-sm p-4">
|
<div class="card shadow-sm p-4">
|
||||||
<h4 class="mb-3">Edit Subcontractor</h4>
|
<h4 class="mb-3">Edit Subcontractor</h4>
|
||||||
|
|
||||||
<form action="/subcontractor/update/{{ subcontractor.id }}" method="POST">
|
<form action="{{ url_for('subcontractor.update_subcontractor', id=subcontractor.id) }}" method="POST">
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Subcontractor Name</label>
|
<label class="form-label">Subcontractor Name</label>
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="btn btn-success">Update</button>
|
<button class="btn btn-success">Update</button>
|
||||||
<a href="/subcontractor/list" class="btn btn-secondary">Back</a>
|
<a href="{{ url_for('subcontractor.subcontractor_list') }}" class="btn btn-secondary">Back</a>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,89 +1,160 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="card shadow-sm p-3 p-md-4">
|
<div class="container mt-4">
|
||||||
|
|
||||||
<!-- Header -->
|
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-3">
|
||||||
<div class="row mb-3 align-items-center">
|
<h4 class="mb-3 mb-md-0">Subcontractor List</h4>
|
||||||
<div class="col-12 col-md-6 ">
|
|
||||||
<h4 class="mb-2 mb-md-0 text-center text-md-start">
|
|
||||||
Subcontractor List
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-12 col-md-3 text-center text-md-end">
|
<a href="{{ url_for('subcontractor.add_subcontractor') }}" class="btn btn-primary btn-sm">
|
||||||
<a href="/subcontractor/add" class="btn btn-success w-100 w-md-auto">
|
+ Add Subcontractor
|
||||||
➕ Add Subcontractor
|
</a>
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body p-2 p-md-3">
|
||||||
|
|
||||||
<div class="table-responsive">
|
<!-- Desktop Table View -->
|
||||||
<table class="table table-bordered table-striped align-middle text-nowrap">
|
<div class="table-responsive d-none d-md-block">
|
||||||
<thead class="table-dark">
|
<table class="table table-bordered table-hover align-middle">
|
||||||
<tr>
|
<thead class="table-dark">
|
||||||
<th>ID</th>
|
<tr>
|
||||||
<th>Name</th>
|
<th>ID</th>
|
||||||
<th class="d-none d-md-table-cell">Mobile</th>
|
<th>Name</th>
|
||||||
<th class="d-none d-lg-table-cell">Email</th>
|
<th>Contact</th>
|
||||||
<th class="d-none d-lg-table-cell">GST No</th>
|
<th>Mobile</th>
|
||||||
<th class="text-center">Action</th>
|
<th>Email</th>
|
||||||
</tr>
|
<th>Status</th>
|
||||||
</thead>
|
<th width="150">Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for s in subcontractors %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ s.id }}</td>
|
||||||
|
<td>{{ s.subcontractor_name }}</td>
|
||||||
|
<td>{{ s.contact_person }}</td>
|
||||||
|
<td>{{ s.mobile_no }}</td>
|
||||||
|
<td>{{ s.email_id }}</td>
|
||||||
|
<td>
|
||||||
|
{% if s.status == "Active" %}
|
||||||
|
<span class="badge bg-success">Active</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-danger">Inactive</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ url_for('subcontractor.edit_subcontractor', id=s.id) }}"
|
||||||
|
class="btn btn-sm btn-warning mb-1">
|
||||||
|
Edit
|
||||||
|
</a>
|
||||||
|
|
||||||
<tbody>
|
<a href="{{ url_for('subcontractor.delete_subcontractor', id=s.id) }}"
|
||||||
|
class="btn btn-sm btn-danger" onclick="return confirm('Are you sure to delete?')">
|
||||||
|
Delete
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile Card View -->
|
||||||
|
<div class="d-md-none">
|
||||||
{% for s in subcontractors %}
|
{% for s in subcontractors %}
|
||||||
<tr>
|
<div class="card mb-3 shadow-sm">
|
||||||
<td>{{ s.id }}</td>
|
<div class="card-body p-3">
|
||||||
|
<h6 class="fw-bold mb-2">{{ s.subcontractor_name }}</h6>
|
||||||
|
|
||||||
<td class="fw-semibold text-wrap">
|
<p class="mb-1"><strong>Contact:</strong> {{ s.contact_person }}</p>
|
||||||
{{ s.subcontractor_name }}
|
<p class="mb-1"><strong>Mobile:</strong> {{ s.mobile_no }}</p>
|
||||||
</td>
|
<p class="mb-1"><strong>Email:</strong> {{ s.email_id }}</p>
|
||||||
|
|
||||||
<td class="d-none d-md-table-cell">
|
<p class="mb-2">
|
||||||
{{ s.mobile_no }}
|
<strong>Status:</strong>
|
||||||
</td>
|
{% if s.status == "Active" %}
|
||||||
|
<span class="badge bg-success">Active</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-danger">Inactive</span>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
|
||||||
<td class="d-none d-lg-table-cell">
|
<div class="d-flex gap-2">
|
||||||
{{ s.email_id }}
|
<a href="{{ url_for('subcontractor.edit_subcontractor', id=s.id) }}"
|
||||||
</td>
|
class="btn btn-sm btn-warning w-50">
|
||||||
|
|
||||||
<td class="d-none d-lg-table-cell">
|
|
||||||
{{ s.gst_no }}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<!-- Action Buttons -->
|
|
||||||
<td class="text-center">
|
|
||||||
<div class="d-flex flex-column gap-1">
|
|
||||||
<a href="/subcontractor/edit/{{ s.id }}" class="btn btn-sm btn-warning">
|
|
||||||
Edit
|
Edit
|
||||||
</a>
|
</a>
|
||||||
<a href="/subcontractor/delete/{{ s.id }}" class="btn btn-sm btn-danger"
|
|
||||||
onclick="return confirm('Are you sure?')">
|
<a href="{{ url_for('subcontractor.delete_subcontractor', id=s.id) }}"
|
||||||
|
class="btn btn-sm btn-danger w-50" onclick="return confirm('Are you sure to delete?')">
|
||||||
Delete
|
Delete
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</div>
|
||||||
|
|
||||||
<!-- TOTAL ROW -->
|
<!-- Pagination -->
|
||||||
<tfoot>
|
<nav>
|
||||||
<tr class="table-secondary fw-bold">
|
<ul class="pagination justify-content-center flex-wrap mt-4">
|
||||||
<td colspan="2" class="text-end"> Total Subcontractors</td>
|
|
||||||
|
|
||||||
<td class="d-none d-lg-table-cell">-</td>
|
{% if pagination.has_prev %}
|
||||||
<td class="d-none d-lg-table-cell">-</td>
|
<li class="page-item">
|
||||||
<td class="d-none d-lg-table-cell">-</td>
|
<a class="page-link" href="{{ url_for('subcontractor.subcontractor_list', page=1) }}">
|
||||||
|
First
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<td class="text-center">{{ subcontractors|length }}</td>
|
<li class="page-item">
|
||||||
|
<a class="page-link"
|
||||||
|
href="{{ url_for('subcontractor.subcontractor_list', page=pagination.prev_num) }}">
|
||||||
|
Prev
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</tr>
|
{% for page_num in pagination.iter_pages() %}
|
||||||
</tfoot>
|
{% if page_num %}
|
||||||
|
{% if page_num != pagination.page %}
|
||||||
|
<li class="page-item">
|
||||||
|
<a class="page-link" href="{{ url_for('subcontractor.subcontractor_list', page=page_num) }}">
|
||||||
|
{{ page_num }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item active">
|
||||||
|
<span class="page-link">{{ page_num }}</span>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled">
|
||||||
|
<span class="page-link">...</span>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
</table>
|
{% if pagination.has_next %}
|
||||||
|
<li class="page-item">
|
||||||
|
<a class="page-link"
|
||||||
|
href="{{ url_for('subcontractor.subcontractor_list', page=pagination.next_num) }}">
|
||||||
|
Next
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="page-item">
|
||||||
|
<a class="page-link"
|
||||||
|
href="{{ url_for('subcontractor.subcontractor_list', page=pagination.pages) }}">
|
||||||
|
Last
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,24 +6,117 @@
|
|||||||
|
|
||||||
<h4 class="mb-3 text-center text-md-start">Subcontractor Dashboard </h4>
|
<h4 class="mb-3 text-center text-md-start">Subcontractor Dashboard </h4>
|
||||||
|
|
||||||
<!-- Charts -->
|
<div class="row g-3 mb-4">
|
||||||
<div class="row g-3">
|
<div class="col-12 col-md-4">
|
||||||
|
<div class="card text-white bg-primary shadow h-100">
|
||||||
|
<div class="card-body text-center text-md-start">
|
||||||
|
<h6>Trenching Units</h6>
|
||||||
|
<h3 class="fw-bold" id="card-trench">0</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<div class="card text-white bg-success shadow h-100">
|
||||||
|
<div class="card-body text-center text-md-start">
|
||||||
|
<h6>Manhole Units</h6>
|
||||||
|
<h3 class="fw-bold" id="card-manhole">0</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<div class="card text-dark bg-warning shadow h-100">
|
||||||
|
<div class="card-body text-center text-md-start">
|
||||||
|
<h6>Laying Units</h6>
|
||||||
|
<h3 class="fw-bold" id="card-laying">0</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Bar Chart -->
|
<div class="row g-3">
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<div class="card shadow-sm h-100">
|
<div class="card shadow-sm h-100">
|
||||||
<div class="card-header bg-dark text-white text-center text-md-start">
|
<div class="card-header bg-dark text-white">Live Category Bar Chart</div>
|
||||||
Work Category Bar Chart
|
<div class="card-body">
|
||||||
</div>
|
<canvas id="liveBarChart" style="max-height:300px;"></canvas>
|
||||||
<div class="card-body text-center">
|
|
||||||
<img src="data:image/png;base64,{{ bar_chart }}" class="img-fluid" style="max-height:300px;">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-md-6">
|
||||||
|
<div class="card shadow-sm h-100">
|
||||||
|
<div class="card-header bg-dark text-white">Location Distribution Pie Chart</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<canvas id="livePieChart" style="max-height:300px;"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 2. Initialize the Bar Chart
|
||||||
|
const barCtx = document.getElementById('liveBarChart').getContext('2d');
|
||||||
|
let liveBarChart = new Chart(barCtx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: ['Trenching', 'Manholes', 'Laying'],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Units Completed',
|
||||||
|
data: [0, 0, 0],
|
||||||
|
backgroundColor: ['#0d6efd', '#198754', '#ffc107']
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: { responsive: true, maintainAspectRatio: false }
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Initialize the Pie Chart
|
||||||
|
const pieCtx = document.getElementById('livePieChart').getContext('2d');
|
||||||
|
let livePieChart = new Chart(pieCtx, {
|
||||||
|
type: 'pie',
|
||||||
|
data: {
|
||||||
|
labels: [], // Will be filled from SQL
|
||||||
|
datasets: [{
|
||||||
|
data: [],
|
||||||
|
backgroundColor: ['#0d6efd', '#198754', '#ffc107', '#6f42c1', '#fd7e14']
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: { responsive: true, maintainAspectRatio: false }
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. Function to Fetch Live Data from your Python API
|
||||||
|
function fetchLiveData() {
|
||||||
|
fetch('/dashboard/api/live-stats') // This matches the route we created in the "Kitchen"
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
// Update the Summary Cards
|
||||||
|
document.getElementById('card-trench').innerText = data.summary.trench;
|
||||||
|
document.getElementById('card-manhole').innerText = data.summary.manhole;
|
||||||
|
document.getElementById('card-laying').innerText = data.summary.laying;
|
||||||
|
|
||||||
|
// Update Bar Chart
|
||||||
|
liveBarChart.data.datasets[0].data = [
|
||||||
|
data.summary.trench,
|
||||||
|
data.summary.manhole,
|
||||||
|
data.summary.laying
|
||||||
|
];
|
||||||
|
liveBarChart.update();
|
||||||
|
|
||||||
|
// Update Pie Chart (Location stats)
|
||||||
|
livePieChart.data.labels = Object.keys(data.locations);
|
||||||
|
livePieChart.data.datasets[0].data = Object.values(data.locations);
|
||||||
|
livePieChart.update();
|
||||||
|
})
|
||||||
|
.catch(err => console.error("Error fetching live data:", err));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Check for updates every 10 seconds (Real-time effect)
|
||||||
|
setInterval(fetchLiveData, 10000);
|
||||||
|
fetchLiveData(); // Load immediately on page open
|
||||||
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
8
app/utils/exceptions.py
Normal file
8
app/utils/exceptions.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from app.constants.http_status import HTTPStatus
|
||||||
|
|
||||||
|
class APIException(Exception):
|
||||||
|
def __init__(self, message, status_code=HTTPStatus.BAD_REQUEST, errors=None):
|
||||||
|
self.message = message
|
||||||
|
self.status_code = status_code
|
||||||
|
self.errors = errors
|
||||||
|
super().__init__(self.message)
|
||||||
23
app/utils/response_handler.py
Normal file
23
app/utils/response_handler.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
from flask import jsonify
|
||||||
|
from app.constants.http_status import HTTPStatus
|
||||||
|
|
||||||
|
|
||||||
|
class ResponseHandler:
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def success(message, data=None, status_code=HTTPStatus.OK):
|
||||||
|
return jsonify({
|
||||||
|
"status": "success",
|
||||||
|
"message": message,
|
||||||
|
"data": data if data else {},
|
||||||
|
"errors": []
|
||||||
|
}), status_code
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def error(message, errors=None, status_code=HTTPStatus.BAD_REQUEST):
|
||||||
|
return jsonify({
|
||||||
|
"status": "error",
|
||||||
|
"message": message,
|
||||||
|
"data": {},
|
||||||
|
"errors": errors if errors else []
|
||||||
|
}), status_code
|
||||||
Reference in New Issue
Block a user