05/24 Enhance functionalities
This commit is contained in:
@@ -3,8 +3,10 @@ routes/admin_settings.py — Application settings (email, Groq API, etc.)
|
||||
"""
|
||||
|
||||
import logging
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, flash, session
|
||||
from config import get_settings_dict, set_setting
|
||||
import os
|
||||
import requests as http_requests
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, flash, session, jsonify
|
||||
from config import get_settings_dict, set_setting, get_setting
|
||||
from models import log_action
|
||||
from utils.decorators import admin_required
|
||||
|
||||
@@ -54,3 +56,49 @@ def save_groq():
|
||||
flash("Groq settings saved successfully.", "success")
|
||||
logger.info(f"Groq settings updated by admin_id={admin['id']}.")
|
||||
return redirect(url_for("admin_settings.settings"))
|
||||
|
||||
|
||||
@admin_settings_bp.route("/test-email", methods=["POST"])
|
||||
@admin_required
|
||||
def test_email():
|
||||
from utils.email import send_email
|
||||
admin = session["user"]
|
||||
to = (admin.get("email") or "").strip()
|
||||
if not to:
|
||||
return jsonify({"error": "Your admin account has no email address. Add one in User Management."}), 400
|
||||
try:
|
||||
send_email(
|
||||
to,
|
||||
"Website Checker — SMTP Test",
|
||||
"This is a test email from Website Checker. Your SMTP configuration is working correctly.",
|
||||
)
|
||||
log_action(admin["id"], "TEST_EMAIL", "app_settings", None,
|
||||
f"SMTP test email sent to {to}.")
|
||||
return jsonify({"ok": True, "to": to})
|
||||
except Exception as e:
|
||||
logger.error(f"SMTP test failed: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
@admin_settings_bp.route("/test-groq", methods=["POST"])
|
||||
@admin_required
|
||||
def test_groq():
|
||||
admin = session["user"]
|
||||
api_key = (os.environ.get("GROQ_API_KEY") or "").strip() or get_setting("groq.api_key", "").strip()
|
||||
if not api_key:
|
||||
return jsonify({"error": "No Groq API key is configured. Save one first."}), 400
|
||||
model = get_setting("groq.model", "llama-3.3-70b-versatile")
|
||||
try:
|
||||
resp = http_requests.post(
|
||||
"https://api.groq.com/openai/v1/chat/completions",
|
||||
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
|
||||
json={"model": model, "messages": [{"role": "user", "content": "Hi"}], "max_tokens": 5},
|
||||
timeout=15,
|
||||
)
|
||||
if resp.ok:
|
||||
log_action(admin["id"], "TEST_GROQ", "app_settings", None, "Groq API test successful.")
|
||||
return jsonify({"ok": True, "model": model})
|
||||
return jsonify({"error": f"Groq API returned {resp.status_code}: {resp.text[:200]}"}), 400
|
||||
except Exception as e:
|
||||
logger.error(f"Groq test failed: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
+35
-2
@@ -3,9 +3,13 @@ routes/admin_users.py — User management CRUD routes.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, flash, session
|
||||
from models import get_all_users, create_user, update_user, delete_user
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, flash, session, jsonify
|
||||
from models import (
|
||||
get_all_users, create_user, update_user, delete_user,
|
||||
get_user_by_id, create_password_reset_token, log_action,
|
||||
)
|
||||
from utils.decorators import admin_required
|
||||
from utils.email import send_email
|
||||
|
||||
logger = logging.getLogger("routes.admin_users")
|
||||
admin_users_bp = Blueprint("admin_users", __name__, url_prefix="/admin/users")
|
||||
@@ -82,3 +86,32 @@ def delete(user_id):
|
||||
flash(f"Error deleting user: {e}", "danger")
|
||||
|
||||
return redirect(url_for("admin_users.users_list"))
|
||||
|
||||
|
||||
@admin_users_bp.route("/<int:user_id>/send-reset", methods=["POST"])
|
||||
@admin_required
|
||||
def send_reset(user_id):
|
||||
admin = session["user"]
|
||||
user = get_user_by_id(user_id)
|
||||
if not user:
|
||||
return jsonify({"error": "User not found."}), 404
|
||||
if not user.get("email"):
|
||||
return jsonify({"error": f"User '{user['username']}' has no email address set."}), 400
|
||||
try:
|
||||
token = create_password_reset_token(user_id)
|
||||
reset_url = url_for("auth.reset_password", token=token, _external=True)
|
||||
body = (
|
||||
f"Hi {user.get('full_name') or user['username']},\n\n"
|
||||
f"An administrator has sent you a password reset link for your Website Checker account.\n"
|
||||
f"Click the link below to set a new password (valid for 1 hour):\n\n"
|
||||
f"{reset_url}\n\n"
|
||||
f"If you did not expect this, please contact your administrator.\n"
|
||||
)
|
||||
send_email(user["email"], "Website Checker — Password Reset", body)
|
||||
log_action(admin["id"], "SEND_RESET_LINK", "users", user_id,
|
||||
f"Password reset link sent to '{user['username']}' ({user['email']}).")
|
||||
logger.info(f"Reset email sent to user_id={user_id} by admin_id={admin['id']}.")
|
||||
return jsonify({"ok": True, "to": user["email"]})
|
||||
except Exception as e:
|
||||
logger.error(f"send_reset error for user_id={user_id}: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
+29
-1
@@ -2,9 +2,11 @@
|
||||
routes/bid_tracker.py — Bid / Opportunity tracker routes.
|
||||
"""
|
||||
|
||||
import csv
|
||||
import io
|
||||
import logging
|
||||
from flask import (Blueprint, render_template, request, redirect, url_for,
|
||||
flash, session, jsonify)
|
||||
flash, session, jsonify, Response)
|
||||
from models import (
|
||||
get_all_bids, get_bid, create_bid, update_bid, delete_bid,
|
||||
get_bid_updates, add_bid_update, delete_bid_update, BID_STATUSES,
|
||||
@@ -276,3 +278,29 @@ def send_reminders():
|
||||
flash(f"Failed to send email: {e}", "danger")
|
||||
|
||||
return redirect(url_for("bid_tracker.bids_list"))
|
||||
|
||||
|
||||
@bid_tracker_bp.route("/export.csv")
|
||||
@login_required
|
||||
def export_csv():
|
||||
status = request.args.get("status", "")
|
||||
bids = get_all_bids(status_filter=status, limit=10000, offset=0)
|
||||
buf = io.StringIO()
|
||||
writer = csv.writer(buf)
|
||||
writer.writerow([
|
||||
"ID", "Title", "Source", "Solicitation #", "Status",
|
||||
"Due Date", "URL", "Notes", "Added By", "Created At",
|
||||
])
|
||||
for b in bids:
|
||||
writer.writerow([
|
||||
b["id"], b.get("title", ""), b.get("source", ""),
|
||||
b.get("solicitation_number", ""), b.get("status", ""),
|
||||
str(b.get("due_date") or ""), b.get("url", ""),
|
||||
b.get("notes", ""), b.get("added_by_username", ""),
|
||||
str(b.get("created_at") or ""),
|
||||
])
|
||||
return Response(
|
||||
buf.getvalue(),
|
||||
mimetype="text/csv",
|
||||
headers={"Content-Disposition": "attachment; filename=bids_export.csv"},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user