hey
Browse files- app/__init__.py +11 -4
- app/__pycache__/__init__.cpython-311.pyc +0 -0
- app/__pycache__/__init__.cpython-312.pyc +0 -0
- app/models/__init__.py +1 -0
- app/models/__pycache__/__init__.cpython-312.pyc +0 -0
- app/models/__pycache__/facebook_ad.cpython-312.pyc +0 -0
- app/models/facebook_ad.py +3 -25
- app/routes/__pycache__/dashboard.cpython-312.pyc +0 -0
- app/routes/dashboard.py +12 -8
- app/templates/base.html +2 -0
- app/templates/dashboard/index.html +69 -0
- requirements.txt +1 -1
app/__init__.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
-
from flask import Flask
|
| 2 |
from flask_sqlalchemy import SQLAlchemy
|
| 3 |
from flask_migrate import Migrate
|
| 4 |
-
from flask_login import LoginManager
|
| 5 |
from celery import Celery
|
| 6 |
from config import Config
|
| 7 |
import redis
|
|
@@ -93,7 +93,7 @@ def create_app(config_class=Config):
|
|
| 93 |
try:
|
| 94 |
logger.info("Importing and registering dashboard blueprint...")
|
| 95 |
from .routes.dashboard import dashboard_bp
|
| 96 |
-
app.register_blueprint(dashboard_bp)
|
| 97 |
logger.info("Dashboard blueprint registered successfully")
|
| 98 |
except Exception as e:
|
| 99 |
logger.error(f"Error registering dashboard blueprint: {e}")
|
|
@@ -141,7 +141,14 @@ def create_app(config_class=Config):
|
|
| 141 |
# Add a simple route directly to the app
|
| 142 |
@app.route('/')
|
| 143 |
def index():
|
| 144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
logger.info("Application initialization complete")
|
| 147 |
return app
|
|
|
|
| 1 |
+
from flask import Flask, redirect, url_for
|
| 2 |
from flask_sqlalchemy import SQLAlchemy
|
| 3 |
from flask_migrate import Migrate
|
| 4 |
+
from flask_login import LoginManager, current_user
|
| 5 |
from celery import Celery
|
| 6 |
from config import Config
|
| 7 |
import redis
|
|
|
|
| 93 |
try:
|
| 94 |
logger.info("Importing and registering dashboard blueprint...")
|
| 95 |
from .routes.dashboard import dashboard_bp
|
| 96 |
+
app.register_blueprint(dashboard_bp, url_prefix='/dashboard')
|
| 97 |
logger.info("Dashboard blueprint registered successfully")
|
| 98 |
except Exception as e:
|
| 99 |
logger.error(f"Error registering dashboard blueprint: {e}")
|
|
|
|
| 141 |
# Add a simple route directly to the app
|
| 142 |
@app.route('/')
|
| 143 |
def index():
|
| 144 |
+
if current_user.is_authenticated:
|
| 145 |
+
try:
|
| 146 |
+
return redirect(url_for('dashboard.index'))
|
| 147 |
+
except:
|
| 148 |
+
# Fallback to facebook_ads if dashboard is not available
|
| 149 |
+
return redirect(url_for('facebook_ads.index'))
|
| 150 |
+
else:
|
| 151 |
+
return redirect(url_for('auth.login'))
|
| 152 |
|
| 153 |
logger.info("Application initialization complete")
|
| 154 |
return app
|
app/__pycache__/__init__.cpython-311.pyc
CHANGED
|
Binary files a/app/__pycache__/__init__.cpython-311.pyc and b/app/__pycache__/__init__.cpython-311.pyc differ
|
|
|
app/__pycache__/__init__.cpython-312.pyc
CHANGED
|
Binary files a/app/__pycache__/__init__.cpython-312.pyc and b/app/__pycache__/__init__.cpython-312.pyc differ
|
|
|
app/models/__init__.py
CHANGED
|
@@ -5,6 +5,7 @@ from app import db
|
|
| 5 |
from datetime import datetime
|
| 6 |
from flask_login import UserMixin
|
| 7 |
from werkzeug.security import generate_password_hash, check_password_hash
|
|
|
|
| 8 |
|
| 9 |
class User(UserMixin, db.Model):
|
| 10 |
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
| 5 |
from datetime import datetime
|
| 6 |
from flask_login import UserMixin
|
| 7 |
from werkzeug.security import generate_password_hash, check_password_hash
|
| 8 |
+
from .facebook_ad import FacebookAd
|
| 9 |
|
| 10 |
class User(UserMixin, db.Model):
|
| 11 |
id = db.Column(db.Integer, primary_key=True)
|
app/models/__pycache__/__init__.cpython-312.pyc
CHANGED
|
Binary files a/app/models/__pycache__/__init__.cpython-312.pyc and b/app/models/__pycache__/__init__.cpython-312.pyc differ
|
|
|
app/models/__pycache__/facebook_ad.cpython-312.pyc
CHANGED
|
Binary files a/app/models/__pycache__/facebook_ad.cpython-312.pyc and b/app/models/__pycache__/facebook_ad.cpython-312.pyc differ
|
|
|
app/models/facebook_ad.py
CHANGED
|
@@ -6,6 +6,8 @@ import json
|
|
| 6 |
class FacebookAd(db.Model):
|
| 7 |
"""Model for storing Facebook ads data."""
|
| 8 |
|
|
|
|
|
|
|
| 9 |
id = db.Column(db.Integer, primary_key=True)
|
| 10 |
ad_id = db.Column(db.String(255), index=True, unique=True)
|
| 11 |
advertiser = db.Column(db.String(255), index=True)
|
|
@@ -46,8 +48,6 @@ class FacebookAd(db.Model):
|
|
| 46 |
else:
|
| 47 |
self.image_urls_json = json.dumps(value)
|
| 48 |
|
| 49 |
-
image_urls = property(get_image_urls, set_image_urls)
|
| 50 |
-
|
| 51 |
def get_links(self):
|
| 52 |
"""Get links as a list."""
|
| 53 |
if not self.links_json:
|
|
@@ -64,8 +64,6 @@ class FacebookAd(db.Model):
|
|
| 64 |
else:
|
| 65 |
self.links_json = json.dumps(value)
|
| 66 |
|
| 67 |
-
links = property(get_links, set_links)
|
| 68 |
-
|
| 69 |
def get_metadata(self):
|
| 70 |
"""Get metadata as a dictionary."""
|
| 71 |
if not self.metadata_json:
|
|
@@ -82,8 +80,6 @@ class FacebookAd(db.Model):
|
|
| 82 |
else:
|
| 83 |
self.metadata_json = json.dumps(value)
|
| 84 |
|
| 85 |
-
metadata = property(get_metadata, set_metadata)
|
| 86 |
-
|
| 87 |
def get_topics(self):
|
| 88 |
"""Get topics as a list."""
|
| 89 |
if not self.topics_json:
|
|
@@ -100,8 +96,6 @@ class FacebookAd(db.Model):
|
|
| 100 |
else:
|
| 101 |
self.topics_json = json.dumps(value)
|
| 102 |
|
| 103 |
-
topics = property(get_topics, set_topics)
|
| 104 |
-
|
| 105 |
def get_entities(self):
|
| 106 |
"""Get entities as a list of dictionaries."""
|
| 107 |
if not self.entities_json:
|
|
@@ -118,8 +112,6 @@ class FacebookAd(db.Model):
|
|
| 118 |
else:
|
| 119 |
self.entities_json = json.dumps(value)
|
| 120 |
|
| 121 |
-
entities = property(get_entities, set_entities)
|
| 122 |
-
|
| 123 |
@classmethod
|
| 124 |
def from_scraper_data(cls, data, user_id=None):
|
| 125 |
"""Create a FacebookAd instance from scraper data."""
|
|
@@ -142,20 +134,6 @@ class FacebookAd(db.Model):
|
|
| 142 |
|
| 143 |
return ad
|
| 144 |
|
| 145 |
-
def get_image_urls_limited(self, limit=None):
|
| 146 |
-
"""Get image URLs, optionally limited to a specific number."""
|
| 147 |
-
urls = self.get_image_urls()
|
| 148 |
-
if limit and len(urls) > limit:
|
| 149 |
-
return urls[:limit]
|
| 150 |
-
return urls
|
| 151 |
-
|
| 152 |
-
def get_links_limited(self, limit=None):
|
| 153 |
-
"""Get links, optionally limited to a specific number."""
|
| 154 |
-
links = self.get_links()
|
| 155 |
-
if limit and len(links) > limit:
|
| 156 |
-
return links[:limit]
|
| 157 |
-
return links
|
| 158 |
-
|
| 159 |
def to_dict(self):
|
| 160 |
"""Convert the ad to a dictionary for API responses."""
|
| 161 |
return {
|
|
@@ -171,4 +149,4 @@ class FacebookAd(db.Model):
|
|
| 171 |
'topics': self.get_topics(),
|
| 172 |
'created_at': self.created_at.isoformat() if self.created_at else None,
|
| 173 |
'updated_at': self.updated_at.isoformat() if self.updated_at else None
|
| 174 |
-
}
|
|
|
|
| 6 |
class FacebookAd(db.Model):
|
| 7 |
"""Model for storing Facebook ads data."""
|
| 8 |
|
| 9 |
+
__tablename__ = 'facebook_ads'
|
| 10 |
+
|
| 11 |
id = db.Column(db.Integer, primary_key=True)
|
| 12 |
ad_id = db.Column(db.String(255), index=True, unique=True)
|
| 13 |
advertiser = db.Column(db.String(255), index=True)
|
|
|
|
| 48 |
else:
|
| 49 |
self.image_urls_json = json.dumps(value)
|
| 50 |
|
|
|
|
|
|
|
| 51 |
def get_links(self):
|
| 52 |
"""Get links as a list."""
|
| 53 |
if not self.links_json:
|
|
|
|
| 64 |
else:
|
| 65 |
self.links_json = json.dumps(value)
|
| 66 |
|
|
|
|
|
|
|
| 67 |
def get_metadata(self):
|
| 68 |
"""Get metadata as a dictionary."""
|
| 69 |
if not self.metadata_json:
|
|
|
|
| 80 |
else:
|
| 81 |
self.metadata_json = json.dumps(value)
|
| 82 |
|
|
|
|
|
|
|
| 83 |
def get_topics(self):
|
| 84 |
"""Get topics as a list."""
|
| 85 |
if not self.topics_json:
|
|
|
|
| 96 |
else:
|
| 97 |
self.topics_json = json.dumps(value)
|
| 98 |
|
|
|
|
|
|
|
| 99 |
def get_entities(self):
|
| 100 |
"""Get entities as a list of dictionaries."""
|
| 101 |
if not self.entities_json:
|
|
|
|
| 112 |
else:
|
| 113 |
self.entities_json = json.dumps(value)
|
| 114 |
|
|
|
|
|
|
|
| 115 |
@classmethod
|
| 116 |
def from_scraper_data(cls, data, user_id=None):
|
| 117 |
"""Create a FacebookAd instance from scraper data."""
|
|
|
|
| 134 |
|
| 135 |
return ad
|
| 136 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
def to_dict(self):
|
| 138 |
"""Convert the ad to a dictionary for API responses."""
|
| 139 |
return {
|
|
|
|
| 149 |
'topics': self.get_topics(),
|
| 150 |
'created_at': self.created_at.isoformat() if self.created_at else None,
|
| 151 |
'updated_at': self.updated_at.isoformat() if self.updated_at else None
|
| 152 |
+
}
|
app/routes/__pycache__/dashboard.cpython-312.pyc
CHANGED
|
Binary files a/app/routes/__pycache__/dashboard.cpython-312.pyc and b/app/routes/__pycache__/dashboard.cpython-312.pyc differ
|
|
|
app/routes/dashboard.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
| 1 |
from flask import Blueprint, render_template, request
|
| 2 |
from flask_login import login_required
|
| 3 |
-
from ..models import
|
| 4 |
-
from ..services.ai_processor import AIPipeline
|
| 5 |
|
| 6 |
dashboard_bp = Blueprint('dashboard', __name__)
|
| 7 |
|
|
@@ -11,11 +10,16 @@ def index():
|
|
| 11 |
page = request.args.get('page', 1, type=int)
|
| 12 |
per_page = 10
|
| 13 |
query = request.args.get('query', '')
|
| 14 |
-
sentiment_filter = request.args.get('sentiment', '')
|
| 15 |
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
-
return render_template('dashboard.html',
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from flask import Blueprint, render_template, request
|
| 2 |
from flask_login import login_required
|
| 3 |
+
from ..models import FacebookAd
|
|
|
|
| 4 |
|
| 5 |
dashboard_bp = Blueprint('dashboard', __name__)
|
| 6 |
|
|
|
|
| 10 |
page = request.args.get('page', 1, type=int)
|
| 11 |
per_page = 10
|
| 12 |
query = request.args.get('query', '')
|
|
|
|
| 13 |
|
| 14 |
+
# Use FacebookAd model with correct filter
|
| 15 |
+
if query:
|
| 16 |
+
ads = FacebookAd.query.filter(
|
| 17 |
+
FacebookAd.content.contains(query)
|
| 18 |
+
).paginate(page=page, per_page=per_page)
|
| 19 |
+
else:
|
| 20 |
+
ads = FacebookAd.query.paginate(page=page, per_page=per_page)
|
| 21 |
|
| 22 |
+
return render_template('dashboard/index.html',
|
| 23 |
+
ads=ads,
|
| 24 |
+
query=query,
|
| 25 |
+
title="Dashboard")
|
app/templates/base.html
CHANGED
|
@@ -21,11 +21,13 @@
|
|
| 21 |
<div class="collapse navbar-collapse" id="navbarNav">
|
| 22 |
<ul class="navbar-nav me-auto">
|
| 23 |
{% if current_user.is_authenticated %}
|
|
|
|
| 24 |
<li class="nav-item">
|
| 25 |
<a class="nav-link" href="{{ url_for('google_ads.dashboard') }}">
|
| 26 |
<i class="fab fa-google"></i> Google Ads
|
| 27 |
</a>
|
| 28 |
</li>
|
|
|
|
| 29 |
<li class="nav-item">
|
| 30 |
<a class="nav-link" href="{{ url_for('facebook_ads.index') }}">
|
| 31 |
<i class="fab fa-facebook"></i> Facebook Ads
|
|
|
|
| 21 |
<div class="collapse navbar-collapse" id="navbarNav">
|
| 22 |
<ul class="navbar-nav me-auto">
|
| 23 |
{% if current_user.is_authenticated %}
|
| 24 |
+
{# Temporarily disabled until Google Ads blueprint is fixed
|
| 25 |
<li class="nav-item">
|
| 26 |
<a class="nav-link" href="{{ url_for('google_ads.dashboard') }}">
|
| 27 |
<i class="fab fa-google"></i> Google Ads
|
| 28 |
</a>
|
| 29 |
</li>
|
| 30 |
+
#}
|
| 31 |
<li class="nav-item">
|
| 32 |
<a class="nav-link" href="{{ url_for('facebook_ads.index') }}">
|
| 33 |
<i class="fab fa-facebook"></i> Facebook Ads
|
app/templates/dashboard/index.html
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block content %}
|
| 4 |
+
<div class="container mt-4">
|
| 5 |
+
<h1>{{ title }}</h1>
|
| 6 |
+
|
| 7 |
+
<!-- Search Form -->
|
| 8 |
+
<form method="GET" action="{{ url_for('dashboard.index') }}" class="mb-4">
|
| 9 |
+
<div class="input-group">
|
| 10 |
+
<input type="text" class="form-control" name="query" value="{{ query }}" placeholder="Search ads...">
|
| 11 |
+
<button type="submit" class="btn btn-primary">Search</button>
|
| 12 |
+
</div>
|
| 13 |
+
</form>
|
| 14 |
+
|
| 15 |
+
<!-- Ads List -->
|
| 16 |
+
<div class="row">
|
| 17 |
+
{% if ads.items %}
|
| 18 |
+
{% for ad in ads.items %}
|
| 19 |
+
<div class="col-md-6 mb-4">
|
| 20 |
+
<div class="card">
|
| 21 |
+
<div class="card-body">
|
| 22 |
+
<h5 class="card-title">Ad #{{ ad.id }}</h5>
|
| 23 |
+
<p class="card-text">{{ ad.content[:200] }}...</p>
|
| 24 |
+
<div class="mt-2">
|
| 25 |
+
<a href="{{ url_for('facebook_ads.ad_detail', ad_id=ad.id) }}" class="btn btn-sm btn-primary">View Details</a>
|
| 26 |
+
</div>
|
| 27 |
+
</div>
|
| 28 |
+
</div>
|
| 29 |
+
</div>
|
| 30 |
+
{% endfor %}
|
| 31 |
+
{% else %}
|
| 32 |
+
<div class="col-12">
|
| 33 |
+
<div class="alert alert-info">
|
| 34 |
+
No ads found. Try a different search query.
|
| 35 |
+
</div>
|
| 36 |
+
</div>
|
| 37 |
+
{% endif %}
|
| 38 |
+
</div>
|
| 39 |
+
|
| 40 |
+
<!-- Pagination -->
|
| 41 |
+
{% if ads.pages > 1 %}
|
| 42 |
+
<nav aria-label="Page navigation" class="mt-4">
|
| 43 |
+
<ul class="pagination justify-content-center">
|
| 44 |
+
{% if ads.has_prev %}
|
| 45 |
+
<li class="page-item">
|
| 46 |
+
<a class="page-link" href="{{ url_for('dashboard.index', page=ads.prev_num, query=query) }}">Previous</a>
|
| 47 |
+
</li>
|
| 48 |
+
{% endif %}
|
| 49 |
+
|
| 50 |
+
{% for page_num in ads.iter_pages(left_edge=2, left_current=2, right_current=3, right_edge=2) %}
|
| 51 |
+
{% if page_num %}
|
| 52 |
+
<li class="page-item {% if page_num == ads.page %}active{% endif %}">
|
| 53 |
+
<a class="page-link" href="{{ url_for('dashboard.index', page=page_num, query=query) }}">{{ page_num }}</a>
|
| 54 |
+
</li>
|
| 55 |
+
{% else %}
|
| 56 |
+
<li class="page-item disabled"><span class="page-link">...</span></li>
|
| 57 |
+
{% endif %}
|
| 58 |
+
{% endfor %}
|
| 59 |
+
|
| 60 |
+
{% if ads.has_next %}
|
| 61 |
+
<li class="page-item">
|
| 62 |
+
<a class="page-link" href="{{ url_for('dashboard.index', page=ads.next_num, query=query) }}">Next</a>
|
| 63 |
+
</li>
|
| 64 |
+
{% endif %}
|
| 65 |
+
</ul>
|
| 66 |
+
</nav>
|
| 67 |
+
{% endif %}
|
| 68 |
+
</div>
|
| 69 |
+
{% endblock %}
|
requirements.txt
CHANGED
|
@@ -14,7 +14,7 @@ requests==2.31.0
|
|
| 14 |
beautifulsoup4==4.12.3
|
| 15 |
selenium==4.17.2
|
| 16 |
transformers==4.37.2
|
| 17 |
-
torch==2.
|
| 18 |
textblob==0.17.1
|
| 19 |
spacy==3.7.2
|
| 20 |
en-core-web-sm @ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.7.1/en_core_web_sm-3.7.1-py3-none-any.whl
|
|
|
|
| 14 |
beautifulsoup4==4.12.3
|
| 15 |
selenium==4.17.2
|
| 16 |
transformers==4.37.2
|
| 17 |
+
torch==2.2.2
|
| 18 |
textblob==0.17.1
|
| 19 |
spacy==3.7.2
|
| 20 |
en-core-web-sm @ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.7.1/en_core_web_sm-3.7.1-py3-none-any.whl
|