import os from flask import Flask, render_template, request, redirect, url_for, session, flash, g, jsonify from dotenv import load_dotenv import sqlite3 from datetime import timedelta import requests from google.oauth2 import service_account from googleapiclient.discovery import build import json import logging load_dotenv() app = Flask(__name__) app.secret_key = os.getenv('FLASK_SECRET_KEY', os.urandom(24)) app.permanent_session_lifetime = timedelta(minutes=30) app.config['SESSION_COOKIE_PATH'] = '/' USERNAME = os.getenv('FLASK_LOGIN_USER') PASSWORD = os.getenv('FLASK_LOGIN_PASSWORD') DATABASE = 'work_orders.db' PUSHOVER_API_TOKEN = os.getenv('PUSHOVER_API_TOKEN') PUSHOVER_USER_KEY = os.getenv('PUSHOVER_USER_KEY') GA_PROPERTY_ID = os.getenv('GA_PROPERTY_ID') GA_CREDENTIALS = 'ga_key.json' logging.basicConfig( filename='/var/log/ga4_debug.log', # Use an absolute path in production level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s' ) def get_db(): db = getattr(g, '_database', None) if db is None: db = g._database = sqlite3.connect(DATABASE) return db @app.teardown_appcontext def close_connection(exception): db = getattr(g, '_database', None) if db is not None: db.close() def init_db(): with app.app_context(): db = get_db() # Orders table db.execute('''CREATE TABLE IF NOT EXISTS orders ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, job TEXT NOT NULL, address TEXT NOT NULL, city TEXT NOT NULL, state TEXT NOT NULL, zipcode INTEGER NOT NULL, phone TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );''') # Completed Orders table db.execute('''CREATE TABLE IF NOT EXISTS completed_orders ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, job TEXT NOT NULL, address TEXT NOT NULL, city TEXT NOT NULL, state TEXT NOT NULL, zipcode INTEGER NOT NULL, phone TEXT NOT NULL, completed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );''') db.commit() @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': username = request.form['username'] password = request.form['password'] if username == USERNAME and password == PASSWORD: session.permanent = True session['logged_in'] = True return redirect(url_for('admin')) else: flash('Invalid username or password.') return render_template('login.html') @app.route('/logout', methods=['GET', 'POST']) def logout(): session.pop('logged_in', None) flash('You have been logged out.') return redirect(url_for('index')) @app.route('/') def index(): return render_template('form.html') @app.route('/submit', methods=['POST']) def submit(): try: name = request.form.get('name') job = request.form.get('job') address = request.form.get('address') city = request.form.get('city') state = request.form.get('state') zipcode = request.form.get('zipcode') phone = request.form.get('phone') conn = get_db() conn.execute('''INSERT INTO orders (name, job, address, city, state, zipcode, phone) VALUES (?, ?, ?, ?, ?, ?, ?)''', (name, job, address, city, state, zipcode, phone)) conn.commit() conn.close() send_push_notification(name, job) session['form_submitted'] = True return redirect(url_for('success')) except Exception as e: session['form_submitted'] = True return redirect(url_for('failure')) @app.route('/success') def success(): if not session.pop('form_submitted', None): return redirect(url_for('index')) return render_template('success.html') @app.route('/failure') def failure(): if not session.pop('form_submitted', None): return redirect(url_for('index')) return render_template('failure.html') @app.route('/admin') def admin(): if not session.get('logged_in'): return redirect(url_for('login')) conn = get_db() orders = conn.execute('SELECT * FROM orders').fetchall() conn.close() return render_template('admin.html', work_orders=orders) @app.route('/delete_order/', methods=['POST']) def delete_order(order_id): if not session.get('logged_in'): return redirect(url_for('login')) try: db = get_db() db.execute('DELETE FROM orders WHERE id = ?', (order_id,)) db.commit() flash('Work order deleted successfully.', 'success') except Exception as e: flash(f'Error deleting work order: {str(e)}', 'danger') return redirect(url_for('admin')) @app.route('/mark_complete/', methods=['POST']) def mark_complete(order_id): if not session.get('logged_in'): return redirect(url_for('login')) try: db = get_db() # Fetch the order to mark as complete order = db.execute('SELECT * FROM orders WHERE id = ?', (order_id,)).fetchone() if order: db.execute('INSERT INTO completed_orders (name, job, address, city, state, zipcode, phone) VALUES (?, ?, ?, ?, ?, ?, ?)', (order[1], order[2], order[3], order[4], order[5], order[6], order[7])) db.execute('DELETE FROM orders WHERE id = ?', (order_id,)) db.commit() flash('Work order marked as complete.', 'success') else: flash('Work order not found.', 'danger') except Exception as e: flash(f'Error marking work order as complete: {str(e)}', 'danger') return redirect(url_for('admin')) @app.route('/completed_jobs') def completed_jobs(): if not session.get('logged_in'): return redirect(url_for('login')) db = get_db() jobs = db.execute('SELECT * FROM completed_orders').fetchall() return render_template('completed_jobs.html', completed_jobs=jobs) def send_push_notification(name, job): if not PUSHOVER_API_TOKEN or not PUSHOVER_USER_KEY: print("Pushover API token or user key is missing.") return message = f"New Work Order:\nName: {name}\nJob: {job}" data = { "token": PUSHOVER_API_TOKEN, "user": PUSHOVER_USER_KEY, "message": message } response = requests.post("https://api.pushover.net/1/messages.json", data=data) if response.status_code == 200: print("Push notification sent!") else: print(f"Failed to send notification: {response.status_code} - {response.text}") def get_ga4_data(): """Fetch website analytics data from Google Analytics 4 (GA4) and handle missing data.""" try: credentials = service_account.Credentials.from_service_account_file( GA_CREDENTIALS, scopes=["https://www.googleapis.com/auth/analytics.readonly"] ) analytics = build("analyticsdata", "v1beta", credentials=credentials) response = analytics.properties().runReport( property=f"properties/{int(GA_PROPERTY_ID)}", body={ "dateRanges": [{"startDate": "7daysAgo", "endDate": "yesterday"}], "metrics": [{"name": "totalUsers"}, {"name": "screenPageViews"}], "dimensions": [{"name": "pagePath"}], "limit": 1, }, ).execute() logging.debug("Full GA4 API Response: %s", json.dumps(response, indent=2)) # Extract top page from rows rows = response.get("rows", []) if not rows: logging.error("No rows found in GA4 response!") return {"total_users": "0", "total_pageviews": "0", "top_page": "N/A"} top_page = rows[0]["dimensionValues"][0]["value"] if rows else "N/A" # Extract users and pageviews from the first row metric_values = rows[0]["metricValues"] total_users = metric_values[0]["value"] if len(metric_values) > 0 else "0" total_pageviews = metric_values[1]["value"] if len(metric_values) > 1 else "0" logging.debug(f"Extracted Users: {total_users}, Pageviews: {total_pageviews}") return {"total_users": total_users, "total_pageviews": total_pageviews, "top_page": top_page} except Exception as e: logging.error("Google Analytics API Error: %s", str(e)) return {"total_users": "0", "total_pageviews": "0", "top_page": "N/A"} @app.route('/analytics') def analytics(): if not session.get('logged_in'): return redirect(url_for('login')) return jsonify(get_ga4_data()) if __name__ == '__main__': init_db() app.run(debug=True)