diff --git a/app/__init__.py b/app/__init__.py index 6c5342c..0e5e99c 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,11 +1,11 @@ from flask import Flask, request, jsonify, render_template import logging +import os from pathlib import Path from .handlers.webhook_handler import handle_payment_webhook from .handlers.payment_handler import BTCPayHandler from .utils.db.operations import DatabaseManager -# Set up logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' @@ -13,8 +13,14 @@ logging.basicConfig( logger = logging.getLogger(__name__) app = Flask(__name__) +app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY', 'dev-secret-key') + btcpay_handler = BTCPayHandler() +# Register blueprints +from .routes.user import user_bp +app.register_blueprint(user_bp, url_prefix='/user') + # Existing webhook route @app.route('/webhook/vpn', methods=['POST']) def handle_payment(): diff --git a/app/routes/__init__.py b/app/routes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/routes/user.py b/app/routes/user.py new file mode 100644 index 0000000..a0e8b21 --- /dev/null +++ b/app/routes/user.py @@ -0,0 +1,86 @@ +from flask import Blueprint, render_template, request, redirect, url_for, flash, session, jsonify +from ..utils.db.operations import DatabaseManager +from ..utils.db.models import SubscriptionStatus +from datetime import datetime +import logging + +logger = logging.getLogger(__name__) +user_bp = Blueprint('user', __name__) + +@user_bp.route('/login', methods=['GET', 'POST']) +def login(): + if request.method == 'POST': + user_id = request.form.get('user_id') + if not user_id: + return jsonify({'error': 'User ID is required'}), 400 + + user = DatabaseManager.get_user_by_uuid(user_id) + if user: + session['user_id'] = user_id + return jsonify({'redirect': url_for('user.dashboard')}) + return jsonify({'error': 'Invalid User ID'}), 401 + + return render_template('user/login.html') + +@user_bp.route('/dashboard') +def dashboard(): + if 'user_id' not in session: + return redirect(url_for('user.login')) + + user = DatabaseManager.get_user_by_uuid(session['user_id']) + if not user: + session.pop('user_id', None) + return redirect(url_for('user.login')) + + subscriptions = DatabaseManager.get_user_subscriptions(user.id) + active_subscription = next( + (sub for sub in subscriptions + if sub.status == SubscriptionStatus.ACTIVE), + None + ) + + subscription_data = [] + for sub in subscriptions: + remaining_time = None + if sub.expiry_time > datetime.utcnow(): + remaining_time = sub.expiry_time - datetime.utcnow() + + subscription_data.append({ + 'id': sub.id, + 'status': sub.status.value, + 'start_time': sub.start_time, + 'expiry_time': sub.expiry_time, + 'remaining_time': str(remaining_time) if remaining_time else None, + 'assigned_ip': sub.assigned_ip + }) + + return render_template( + 'user/dashboard.html', + user=user, + subscriptions=subscription_data, + active_subscription=active_subscription + ) + +@user_bp.route('/logout') +def logout(): + session.pop('user_id', None) + return redirect(url_for('user.login')) + +@user_bp.route('/api/subscription/config/') +def get_subscription_config(subscription_id): + if 'user_id' not in session: + return jsonify({'error': 'Unauthorized'}), 401 + + user = DatabaseManager.get_user_by_uuid(session['user_id']) + if not user: + return jsonify({'error': 'User not found'}), 404 + + subscription = DatabaseManager.get_subscription_by_id(subscription_id) + if not subscription or subscription.user_id != user.id: + return jsonify({'error': 'Subscription not found'}), 404 + + return jsonify({ + 'config': subscription.config_data, + 'assigned_ip': subscription.assigned_ip, + 'expiry_time': subscription.expiry_time.isoformat() + }) diff --git a/app/templates/index.html b/app/templates/index.html index e3cc046..351eccb 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -118,6 +118,13 @@ Pay with Bitcoin +
+

Already have a subscription?

+ + Login to Dashboard + +
diff --git a/app/templates/user/dashboard.html b/app/templates/user/dashboard.html new file mode 100644 index 0000000..c09a207 --- /dev/null +++ b/app/templates/user/dashboard.html @@ -0,0 +1,114 @@ +{% extends "base.html" %} +{% block content %} +
+
+
+

Dashboard

+
+ User ID: {{ user.user_id }} + + Logout + +
+
+ + {% if active_subscription %} +
+

Active Subscription

+
+
+

Status

+

{{ active_subscription.status }}

+
+
+

IP Address

+

{{ active_subscription.assigned_ip }}

+
+
+

Expires

+

{{ active_subscription.expiry_time.strftime('%Y-%m-%d %H:%M UTC') }}

+
+
+

Time Remaining

+

{{ active_subscription.remaining_time }}

+
+
+ +
+ + +
+
+ {% else %} +
+

No active subscription

+ + Subscribe Now + +
+ {% endif %} + +
+

Subscription History

+
+ + + + + + + + + + + {% for sub in subscriptions %} + + + + + + + {% endfor %} + +
StatusStart DateEnd DateIP Address
{{ sub.status }}{{ sub.start_time.strftime('%Y-%m-%d %H:%M UTC') }}{{ sub.expiry_time.strftime('%Y-%m-%d %H:%M UTC') }}{{ sub.assigned_ip }}
+
+
+
+
+ + +{% endblock %} \ No newline at end of file diff --git a/app/templates/user/login.html b/app/templates/user/login.html new file mode 100644 index 0000000..e0e79cb --- /dev/null +++ b/app/templates/user/login.html @@ -0,0 +1,56 @@ +{% extends "base.html" %} +{% block content %} +
+
+

Login to Dashboard

+ +
+
+ + +

Enter the User ID you received during subscription

+
+ + +
+ +
+

Don't have a subscription?

+ + Subscribe Now + +
+
+
+ + +{% endblock %} \ No newline at end of file diff --git a/app/utils/db/__init__.py b/app/utils/db/__init__.py index 8fdadd7..22b98ba 100644 --- a/app/utils/db/__init__.py +++ b/app/utils/db/__init__.py @@ -3,7 +3,7 @@ from sqlalchemy.orm import sessionmaker from pathlib import Path def get_db_path(): - base_dir = Path(__file__).resolve().parent.parent.parent + base_dir = Path(__file__).resolve().parent.parent.parent.parent data_dir = base_dir / 'data' data_dir.mkdir(exist_ok=True) return data_dir / 'vpn.db' diff --git a/app/utils/db/operations.py b/app/utils/db/operations.py index 4540451..186a249 100644 --- a/app/utils/db/operations.py +++ b/app/utils/db/operations.py @@ -13,13 +13,11 @@ class DatabaseManager: def get_next_available_ip(): """Get the next available IP from the WireGuard subnet""" with get_session() as session: - # Get all assigned IPs assigned_ips = session.query(Subscription.assigned_ip)\ .filter(Subscription.assigned_ip.isnot(None))\ .all() assigned_ips = [ip[0] for ip in assigned_ips] - # Start from 10.8.0.2 (10.8.0.1 is server) network = ipaddress.IPv4Network('10.8.0.0/24') for ip in network.hosts(): str_ip = str(ip) @@ -53,12 +51,37 @@ class DatabaseManager: .filter(Subscription.invoice_id == invoice_id)\ .first() + @staticmethod + def get_subscription_by_id(subscription_id): + """Get subscription by ID""" + with get_session() as session: + return session.query(Subscription)\ + .filter(Subscription.id == subscription_id)\ + .first() + + @staticmethod + def get_user_subscriptions(user_id): + """Get all subscriptions for a user""" + with get_session() as session: + return session.query(Subscription)\ + .filter(Subscription.user_id == user_id)\ + .order_by(Subscription.start_time.desc())\ + .all() + + @staticmethod + def get_active_subscription_for_user(user_id): + """Get active subscription for a user""" + with get_session() as session: + return session.query(Subscription)\ + .filter(Subscription.user_id == user_id)\ + .filter(Subscription.status == SubscriptionStatus.ACTIVE)\ + .first() + @staticmethod def create_subscription(user_id, invoice_id, public_key, duration_hours): """Create a new subscription""" with get_session() as session: try: - # Get user or create if doesn't exist user = session.query(User).filter(User.user_id == user_id).first() if not user: user = User(user_id=user_id) @@ -67,8 +90,6 @@ class DatabaseManager: start_time = datetime.utcnow() expiry_time = start_time + timedelta(hours=duration_hours) - - # Get next available IP assigned_ip = DatabaseManager.get_next_available_ip() subscription = Subscription( @@ -84,7 +105,6 @@ class DatabaseManager: session.add(subscription) session.commit() - # Return a dictionary of values instead of the SQLAlchemy object return { 'id': subscription.id, 'user_id': user.id, diff --git a/data/vpn.db b/data/vpn.db index 28a0b83..f6675f5 100644 Binary files a/data/vpn.db and b/data/vpn.db differ