rastof9 commited on
Commit
d98cb87
·
1 Parent(s): f788a29
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
@@ -4,94 +4,157 @@ import uuid
4
  import json
5
 
6
  class FacebookAd(db.Model):
7
- """Model for storing Facebook Ads data."""
8
- id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
9
 
10
- # Facebook-specific fields
11
- ad_id = db.Column(db.String(255), nullable=True, index=True)
12
- advertiser = db.Column(db.String(255), nullable=True, index=True)
13
- advertiser_id = db.Column(db.String(255), nullable=True, index=True)
 
 
14
 
15
- # Content fields
16
- content = db.Column(db.Text, nullable=True)
17
- images = db.Column(db.JSON, nullable=True) # URLs to images
18
- links = db.Column(db.JSON, nullable=True) # URLs in the ad
19
-
20
- # Search metadata
21
- search_query = db.Column(db.String(255), nullable=True, index=True)
22
- position = db.Column(db.Integer, nullable=True)
23
 
24
  # Analysis results
25
- sentiment = db.Column(db.JSON, nullable=True)
26
- topics = db.Column(db.JSON, nullable=True)
27
- entities = db.Column(db.JSON, nullable=True)
28
-
29
- # Raw data for future processing
30
- raw_data = db.Column(db.JSON, nullable=True)
31
- raw_text = db.Column(db.Text, nullable=True)
32
 
33
  # Timestamps
34
  created_at = db.Column(db.DateTime, default=datetime.utcnow)
35
  updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
36
 
37
- # User association
38
  user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True)
39
 
40
- def __repr__(self):
41
- return f'<FacebookAd {self.id} - {self.advertiser}>'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
  @classmethod
44
- def from_scraper_data(cls, ad_data, user_id=None):
45
- """Create a FacebookAd instance from scraped data."""
46
- # Extract fields from the scraped data
 
 
47
  ad = cls(
48
- ad_id=ad_data.get('ad_id'),
49
- advertiser=ad_data.get('advertiser'),
50
- advertiser_id=ad_data.get('advertiser_id'),
51
- content=ad_data.get('content'),
52
- raw_text=ad_data.get('raw_text'),
53
- search_query=ad_data.get('search_query'),
54
- position=ad_data.get('position'),
55
  user_id=user_id
56
  )
57
 
58
- # Handle JSON fields
59
- if 'images' in ad_data and ad_data['images']:
60
- ad.images = ad_data['images']
61
-
62
- if 'links' in ad_data and ad_data['links']:
63
- ad.links = ad_data['links']
64
-
65
- # Store the full raw data for future reference
66
- ad.raw_data = {k: v for k, v in ad_data.items() if k not in ['images', 'links']}
67
 
68
  return ad
69
 
70
- def get_image_urls(self):
71
- """Get list of image URLs from the ad."""
72
- if not self.images:
73
- return []
74
-
75
- if isinstance(self.images, str):
76
- try:
77
- return json.loads(self.images)
78
- except:
79
- return []
80
-
81
- return self.images
82
 
83
- def get_links(self):
84
- """Get list of links from the ad."""
85
- if not self.links:
86
- return []
87
-
88
- if isinstance(self.links, str):
89
- try:
90
- return json.loads(self.links)
91
- except:
92
- return []
93
-
94
- return self.links
95
 
96
  def to_dict(self):
97
  """Convert the ad to a dictionary for API responses."""
@@ -101,13 +164,11 @@ class FacebookAd(db.Model):
101
  'advertiser': self.advertiser,
102
  'advertiser_id': self.advertiser_id,
103
  'content': self.content,
104
- 'images': self.get_image_urls(),
105
- 'links': self.get_links(),
106
  'search_query': self.search_query,
107
- 'position': self.position,
 
108
  'sentiment': self.sentiment,
109
- 'topics': self.topics,
110
- 'entities': self.entities,
111
  'created_at': self.created_at.isoformat() if self.created_at else None,
112
  'updated_at': self.updated_at.isoformat() if self.updated_at else None
113
  }
 
4
  import json
5
 
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)
12
+ advertiser_id = db.Column(db.String(255), index=True, nullable=True)
13
+ content = db.Column(db.Text)
14
+ search_query = db.Column(db.String(255), index=True, nullable=True)
15
 
16
+ # Store JSON data as text fields
17
+ image_urls_json = db.Column(db.Text, nullable=True)
18
+ links_json = db.Column(db.Text, nullable=True)
19
+ metadata_json = db.Column(db.Text, nullable=True)
20
+ topics_json = db.Column(db.Text, nullable=True)
21
+ entities_json = db.Column(db.Text, nullable=True)
 
 
22
 
23
  # Analysis results
24
+ sentiment = db.Column(db.Float, nullable=True)
 
 
 
 
 
 
25
 
26
  # Timestamps
27
  created_at = db.Column(db.DateTime, default=datetime.utcnow)
28
  updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
29
 
30
+ # User relationship
31
  user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True)
32
 
33
+ def get_image_urls(self):
34
+ """Get image URLs as a list."""
35
+ if not self.image_urls_json:
36
+ return []
37
+ try:
38
+ return json.loads(self.image_urls_json)
39
+ except:
40
+ return []
41
+
42
+ def set_image_urls(self, value):
43
+ """Set image URLs from a list."""
44
+ if value is None:
45
+ self.image_urls_json = None
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:
54
+ return []
55
+ try:
56
+ return json.loads(self.links_json)
57
+ except:
58
+ return []
59
+
60
+ def set_links(self, value):
61
+ """Set links from a list."""
62
+ if value is None:
63
+ self.links_json = None
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:
72
+ return {}
73
+ try:
74
+ return json.loads(self.metadata_json)
75
+ except:
76
+ return {}
77
+
78
+ def set_metadata(self, value):
79
+ """Set metadata from a dictionary."""
80
+ if value is None:
81
+ self.metadata_json = None
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:
90
+ return []
91
+ try:
92
+ return json.loads(self.topics_json)
93
+ except:
94
+ return []
95
+
96
+ def set_topics(self, value):
97
+ """Set topics from a list."""
98
+ if value is None:
99
+ self.topics_json = None
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:
108
+ return []
109
+ try:
110
+ return json.loads(self.entities_json)
111
+ except:
112
+ return []
113
+
114
+ def set_entities(self, value):
115
+ """Set entities from a list of dictionaries."""
116
+ if value is None:
117
+ self.entities_json = None
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."""
126
+ # Generate a unique ID if not provided
127
+ ad_id = data.get('ad_id', str(uuid.uuid4()))
128
+
129
  ad = cls(
130
+ ad_id=ad_id,
131
+ advertiser=data.get('advertiser', ''),
132
+ advertiser_id=data.get('advertiser_id'),
133
+ content=data.get('content', ''),
134
+ search_query=data.get('search_query'),
 
 
135
  user_id=user_id
136
  )
137
 
138
+ # Set JSON fields
139
+ ad.set_image_urls(data.get('image_urls', []))
140
+ ad.set_links(data.get('links', []))
141
+ ad.set_metadata(data.get('metadata', {}))
 
 
 
 
 
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."""
 
164
  'advertiser': self.advertiser,
165
  'advertiser_id': self.advertiser_id,
166
  'content': self.content,
 
 
167
  'search_query': self.search_query,
168
+ 'image_urls': self.get_image_urls(),
169
+ 'links': self.get_links(),
170
  'sentiment': self.sentiment,
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
  }
app/routes/__pycache__/facebook_ads.cpython-312.pyc CHANGED
Binary files a/app/routes/__pycache__/facebook_ads.cpython-312.pyc and b/app/routes/__pycache__/facebook_ads.cpython-312.pyc differ
 
app/routes/facebook_ads.py CHANGED
@@ -1,9 +1,6 @@
1
  from flask import Blueprint, render_template, request, jsonify, current_app, flash, redirect, url_for
2
  from flask_login import login_required, current_user
3
- from app.services.facebook_scraper import FacebookScraper
4
- from app.models.facebook_ad import FacebookAd
5
- from app.services.ai_processor import AIPipeline
6
- from app import db, celery
7
  import logging
8
  import json
9
  from datetime import datetime
@@ -11,11 +8,14 @@ from datetime import datetime
11
  logger = logging.getLogger(__name__)
12
  facebook_ads_bp = Blueprint('facebook_ads', __name__, url_prefix='/facebook-ads')
13
 
 
 
 
14
  @facebook_ads_bp.route('/', methods=['GET'])
15
  @login_required
16
  def index():
17
  """Facebook Ads dashboard page."""
18
- return render_template('facebook_ads/index.html')
19
 
20
  @facebook_ads_bp.route('/search', methods=['GET', 'POST'])
21
  @login_required
@@ -23,20 +23,15 @@ def search():
23
  """Search for Facebook ads."""
24
  if request.method == 'POST':
25
  search_query = request.form.get('search_query', '')
26
- num_scrolls = int(request.form.get('num_scrolls', 5))
27
- country_code = request.form.get('country_code', 'ALL')
28
 
29
  if not search_query:
30
  flash('Please enter a search query', 'warning')
31
- return render_template('facebook_ads/search.html')
32
-
33
- # Start the scraping task
34
- task = scrape_facebook_ads.delay(search_query, num_scrolls, country_code, current_user.id)
35
 
36
- flash(f'Started scraping Facebook ads for "{search_query}". This may take a few minutes.', 'info')
37
- return render_template('facebook_ads/search.html', task_id=task.id)
38
 
39
- return render_template('facebook_ads/search.html')
40
 
41
  @facebook_ads_bp.route('/page-search', methods=['GET', 'POST'])
42
  @login_required
@@ -44,106 +39,82 @@ def page_search():
44
  """Search for ads from a specific Facebook page."""
45
  if request.method == 'POST':
46
  page_name = request.form.get('page_name', '')
47
- num_scrolls = int(request.form.get('num_scrolls', 5))
48
 
49
  if not page_name:
50
  flash('Please enter a page name', 'warning')
51
- return render_template('facebook_ads/page_search.html')
52
-
53
- # Start the scraping task
54
- task = scrape_facebook_page_ads.delay(page_name, num_scrolls, current_user.id)
55
 
56
- flash(f'Started scraping Facebook ads for page "{page_name}". This may take a few minutes.', 'info')
57
- return render_template('facebook_ads/page_search.html', task_id=task.id)
58
 
59
- return render_template('facebook_ads/page_search.html')
60
 
61
  @facebook_ads_bp.route('/results', methods=['GET'])
62
  @login_required
63
  def results():
64
  """View Facebook ads results."""
65
- ad_type = request.args.get('type', 'all')
 
66
  query = request.args.get('query', '')
67
  advertiser = request.args.get('advertiser', '')
68
 
69
- # Build query
70
- ads_query = FacebookAd.query
71
-
72
- if query:
73
- ads_query = ads_query.filter(FacebookAd.search_query.ilike(f'%{query}%'))
74
-
75
- if advertiser:
76
- ads_query = ads_query.filter(FacebookAd.advertiser.ilike(f'%{advertiser}%'))
77
-
78
- # Get results
79
- ads = ads_query.order_by(FacebookAd.created_at.desc()).limit(100).all()
80
-
81
- return render_template('facebook_ads/results.html', ads=ads, query=query, advertiser=advertiser)
82
 
83
  @facebook_ads_bp.route('/ad/<ad_id>', methods=['GET'])
84
  @login_required
85
  def view_ad(ad_id):
86
  """View details of a specific Facebook ad."""
87
- ad = FacebookAd.query.get_or_404(ad_id)
88
- return render_template('facebook_ads/ad_detail.html', ad=ad)
 
 
 
 
 
 
 
 
89
 
90
  @facebook_ads_bp.route('/advertisers', methods=['GET'])
91
  @login_required
92
  def advertisers():
93
  """View list of advertisers."""
94
- # Get unique advertisers and count their ads
95
- advertisers_data = db.session.query(
96
- FacebookAd.advertiser,
97
- db.func.count(FacebookAd.id).label('ad_count')
98
- ).group_by(FacebookAd.advertiser).order_by(db.func.count(FacebookAd.id).desc()).limit(100).all()
99
 
100
- return render_template('facebook_ads/advertisers.html', advertisers=advertisers_data)
101
 
102
  @facebook_ads_bp.route('/advertiser/<advertiser_name>', methods=['GET'])
103
  @login_required
104
  def advertiser_detail(advertiser_name):
105
  """View details and ads for a specific advertiser."""
106
- ads = FacebookAd.query.filter(FacebookAd.advertiser == advertiser_name).order_by(FacebookAd.created_at.desc()).all()
107
- return render_template('facebook_ads/advertiser_detail.html', advertiser=advertiser_name, ads=ads)
 
108
 
109
  @facebook_ads_bp.route('/analyze/<ad_id>', methods=['GET'])
110
  @login_required
111
  def analyze_ad(ad_id):
112
  """Analyze a specific Facebook ad."""
113
- ad = FacebookAd.query.get_or_404(ad_id)
114
-
115
- # Start the analysis task if not already analyzed
116
- if not ad.sentiment or not ad.topics:
117
- task = analyze_facebook_ad.delay(ad_id)
118
- flash('Started analyzing the ad. Refresh in a few moments to see results.', 'info')
119
-
120
- return render_template('facebook_ads/ad_analysis.html', ad=ad)
 
 
 
 
121
 
122
  @facebook_ads_bp.route('/api/ads', methods=['GET'])
123
  @login_required
124
  def api_get_ads():
125
  """API endpoint to get Facebook Ads data."""
126
- query = request.args.get('query', '')
127
- advertiser = request.args.get('advertiser', '')
128
- limit = int(request.args.get('limit', 50))
129
-
130
- # Build query
131
- ads_query = FacebookAd.query
132
-
133
- if query:
134
- ads_query = ads_query.filter(
135
- (FacebookAd.content.ilike(f'%{query}%')) |
136
- (FacebookAd.search_query.ilike(f'%{query}%'))
137
- )
138
-
139
- if advertiser:
140
- ads_query = ads_query.filter(FacebookAd.advertiser.ilike(f'%{advertiser}%'))
141
-
142
- # Get results
143
- ads = ads_query.order_by(FacebookAd.created_at.desc()).limit(limit).all()
144
-
145
- # Convert to JSON
146
- result = [ad.to_dict() for ad in ads]
147
 
148
  return jsonify(result)
149
 
@@ -151,160 +122,7 @@ def api_get_ads():
151
  @login_required
152
  def api_get_advertisers():
153
  """API endpoint to get advertisers data."""
154
- limit = int(request.args.get('limit', 50))
155
-
156
- # Get unique advertisers and count their ads
157
- advertisers_data = db.session.query(
158
- FacebookAd.advertiser,
159
- db.func.count(FacebookAd.id).label('ad_count')
160
- ).group_by(FacebookAd.advertiser).order_by(db.func.count(FacebookAd.id).desc()).limit(limit).all()
161
-
162
- # Convert to JSON
163
- result = [{"name": adv[0], "ad_count": adv[1]} for adv in advertisers_data if adv[0]]
164
-
165
- return jsonify(result)
166
-
167
- @celery.task
168
- def scrape_facebook_ads(search_query, num_scrolls, country_code, user_id):
169
- """Celery task to scrape Facebook ads."""
170
- try:
171
- logger.info(f"Starting Facebook ads scraping for query: {search_query}")
172
-
173
- # Initialize scraper
174
- scraper = FacebookScraper()
175
-
176
- # Scrape ads
177
- ads_data = scraper.scrape_ads(search_query, num_scrolls, country_code)
178
-
179
- logger.info(f"Scraped {len(ads_data)} Facebook ads")
180
-
181
- # Process and store ads
182
- ai_pipeline = AIPipeline()
183
-
184
- for ad_data in ads_data:
185
- # Create FacebookAd instance
186
- ad = FacebookAd.from_scraper_data(ad_data, user_id)
187
-
188
- # Process with AI if there's content
189
- if ad.content:
190
- try:
191
- # Create a simple object with content for AI processing
192
- ad_content = type('obj', (object,), {
193
- 'content': ad.content
194
- })
195
-
196
- # Process with AI
197
- ai_results = ai_pipeline.process_ad(ad_content)
198
- ad.sentiment = ai_results.get('sentiment')
199
- except Exception as e:
200
- logger.error(f"Error processing ad with AI: {e}")
201
-
202
- # Save to database
203
- db.session.add(ad)
204
-
205
- db.session.commit()
206
- logger.info(f"Saved {len(ads_data)} Facebook ads to database")
207
-
208
- return {'status': 'success', 'count': len(ads_data)}
209
-
210
- except Exception as e:
211
- logger.error(f"Error in Facebook ads scraping task: {e}")
212
- db.session.rollback()
213
- return {'status': 'error', 'message': str(e)}
214
-
215
- @celery.task
216
- def scrape_facebook_page_ads(page_name, num_scrolls, user_id):
217
- """Celery task to scrape ads from a specific Facebook page."""
218
- try:
219
- logger.info(f"Starting Facebook page ads scraping for page: {page_name}")
220
-
221
- # Initialize scraper
222
- scraper = FacebookScraper()
223
-
224
- # Scrape ads
225
- ads_data = scraper.scrape_ads_by_page(page_name, num_scrolls)
226
-
227
- logger.info(f"Scraped {len(ads_data)} Facebook ads from page {page_name}")
228
-
229
- # Process and store ads
230
- ai_pipeline = AIPipeline()
231
-
232
- for ad_data in ads_data:
233
- # Create FacebookAd instance
234
- ad = FacebookAd.from_scraper_data(ad_data, user_id)
235
-
236
- # Process with AI if there's content
237
- if ad.content:
238
- try:
239
- # Create a simple object with content for AI processing
240
- ad_content = type('obj', (object,), {
241
- 'content': ad.content
242
- })
243
-
244
- # Process with AI
245
- ai_results = ai_pipeline.process_ad(ad_content)
246
- ad.sentiment = ai_results.get('sentiment')
247
- except Exception as e:
248
- logger.error(f"Error processing ad with AI: {e}")
249
-
250
- # Save to database
251
- db.session.add(ad)
252
-
253
- db.session.commit()
254
- logger.info(f"Saved {len(ads_data)} Facebook ads to database")
255
-
256
- return {'status': 'success', 'count': len(ads_data)}
257
-
258
- except Exception as e:
259
- logger.error(f"Error in Facebook page ads scraping task: {e}")
260
- db.session.rollback()
261
- return {'status': 'error', 'message': str(e)}
262
-
263
- @celery.task
264
- def analyze_facebook_ad(ad_id):
265
- """Celery task to analyze a Facebook ad."""
266
- try:
267
- logger.info(f"Starting analysis for Facebook ad: {ad_id}")
268
-
269
- # Get the ad
270
- ad = FacebookAd.query.get(ad_id)
271
-
272
- if not ad:
273
- logger.error(f"Ad not found: {ad_id}")
274
- return {'status': 'error', 'message': 'Ad not found'}
275
-
276
- # Initialize AI pipeline
277
- ai_pipeline = AIPipeline()
278
-
279
- # Process with AI if there's content
280
- if ad.content:
281
- try:
282
- # Create a simple object with content for AI processing
283
- ad_content = type('obj', (object,), {
284
- 'content': ad.content
285
- })
286
-
287
- # Process with AI
288
- ai_results = ai_pipeline.process_ad(ad_content)
289
-
290
- # Update ad with results
291
- ad.sentiment = ai_results.get('sentiment')
292
- ad.topics = ai_results.get('topics')
293
- ad.entities = ai_results.get('entities')
294
-
295
- # Save to database
296
- db.session.commit()
297
-
298
- logger.info(f"Successfully analyzed Facebook ad: {ad_id}")
299
- return {'status': 'success', 'ad_id': ad_id}
300
- except Exception as e:
301
- logger.error(f"Error processing ad with AI: {e}")
302
- return {'status': 'error', 'message': str(e)}
303
- else:
304
- logger.warning(f"No content to analyze for ad: {ad_id}")
305
- return {'status': 'warning', 'message': 'No content to analyze'}
306
 
307
- except Exception as e:
308
- logger.error(f"Error in Facebook ad analysis task: {e}")
309
- db.session.rollback()
310
- return {'status': 'error', 'message': str(e)}
 
1
  from flask import Blueprint, render_template, request, jsonify, current_app, flash, redirect, url_for
2
  from flask_login import login_required, current_user
3
+ from app import db
 
 
 
4
  import logging
5
  import json
6
  from datetime import datetime
 
8
  logger = logging.getLogger(__name__)
9
  facebook_ads_bp = Blueprint('facebook_ads', __name__, url_prefix='/facebook-ads')
10
 
11
+ # Flag to indicate if AI processing is available
12
+ AI_AVAILABLE = False
13
+
14
  @facebook_ads_bp.route('/', methods=['GET'])
15
  @login_required
16
  def index():
17
  """Facebook Ads dashboard page."""
18
+ return render_template('facebook_ads/index.html', ai_available=AI_AVAILABLE)
19
 
20
  @facebook_ads_bp.route('/search', methods=['GET', 'POST'])
21
  @login_required
 
23
  """Search for Facebook ads."""
24
  if request.method == 'POST':
25
  search_query = request.form.get('search_query', '')
 
 
26
 
27
  if not search_query:
28
  flash('Please enter a search query', 'warning')
29
+ return render_template('facebook_ads/search.html', ai_available=AI_AVAILABLE)
 
 
 
30
 
31
+ flash('Facebook Ads scraping is not available. Please install required dependencies.', 'warning')
32
+ return render_template('facebook_ads/search.html', ai_available=AI_AVAILABLE)
33
 
34
+ return render_template('facebook_ads/search.html', ai_available=AI_AVAILABLE)
35
 
36
  @facebook_ads_bp.route('/page-search', methods=['GET', 'POST'])
37
  @login_required
 
39
  """Search for ads from a specific Facebook page."""
40
  if request.method == 'POST':
41
  page_name = request.form.get('page_name', '')
 
42
 
43
  if not page_name:
44
  flash('Please enter a page name', 'warning')
45
+ return render_template('facebook_ads/page_search.html', ai_available=AI_AVAILABLE)
 
 
 
46
 
47
+ flash('Facebook Ads scraping is not available. Please install required dependencies.', 'warning')
48
+ return render_template('facebook_ads/page_search.html', ai_available=AI_AVAILABLE)
49
 
50
+ return render_template('facebook_ads/page_search.html', ai_available=AI_AVAILABLE)
51
 
52
  @facebook_ads_bp.route('/results', methods=['GET'])
53
  @login_required
54
  def results():
55
  """View Facebook ads results."""
56
+ # Placeholder for now
57
+ ads = []
58
  query = request.args.get('query', '')
59
  advertiser = request.args.get('advertiser', '')
60
 
61
+ return render_template('facebook_ads/results.html', ads=ads, query=query, advertiser=advertiser, ai_available=AI_AVAILABLE)
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
  @facebook_ads_bp.route('/ad/<ad_id>', methods=['GET'])
64
  @login_required
65
  def view_ad(ad_id):
66
  """View details of a specific Facebook ad."""
67
+ # Placeholder for now
68
+ ad = {
69
+ 'id': ad_id,
70
+ 'advertiser': 'Example Advertiser',
71
+ 'content': 'This is a placeholder ad content.',
72
+ 'image_urls': [],
73
+ 'links': [],
74
+ 'created_at': datetime.utcnow()
75
+ }
76
+ return render_template('facebook_ads/ad_detail.html', ad=ad, ai_available=AI_AVAILABLE)
77
 
78
  @facebook_ads_bp.route('/advertisers', methods=['GET'])
79
  @login_required
80
  def advertisers():
81
  """View list of advertisers."""
82
+ # Placeholder for now
83
+ advertisers_data = []
 
 
 
84
 
85
+ return render_template('facebook_ads/advertisers.html', advertisers=advertisers_data, ai_available=AI_AVAILABLE)
86
 
87
  @facebook_ads_bp.route('/advertiser/<advertiser_name>', methods=['GET'])
88
  @login_required
89
  def advertiser_detail(advertiser_name):
90
  """View details and ads for a specific advertiser."""
91
+ # Placeholder for now
92
+ ads = []
93
+ return render_template('facebook_ads/advertiser_detail.html', advertiser=advertiser_name, ads=ads, ai_available=AI_AVAILABLE)
94
 
95
  @facebook_ads_bp.route('/analyze/<ad_id>', methods=['GET'])
96
  @login_required
97
  def analyze_ad(ad_id):
98
  """Analyze a specific Facebook ad."""
99
+ # Placeholder for now
100
+ ad = {
101
+ 'id': ad_id,
102
+ 'advertiser': 'Example Advertiser',
103
+ 'content': 'This is a placeholder ad content.',
104
+ 'image_urls': [],
105
+ 'links': [],
106
+ 'created_at': datetime.utcnow()
107
+ }
108
+
109
+ flash('AI analysis is not available. Please install required dependencies.', 'warning')
110
+ return render_template('facebook_ads/ad_analysis.html', ad=ad, ai_available=AI_AVAILABLE)
111
 
112
  @facebook_ads_bp.route('/api/ads', methods=['GET'])
113
  @login_required
114
  def api_get_ads():
115
  """API endpoint to get Facebook Ads data."""
116
+ # Placeholder for now
117
+ result = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
  return jsonify(result)
120
 
 
122
  @login_required
123
  def api_get_advertisers():
124
  """API endpoint to get advertisers data."""
125
+ # Placeholder for now
126
+ result = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
+ return jsonify(result)
 
 
 
app/templates/base.html CHANGED
@@ -14,52 +14,81 @@
14
  <body>
15
  <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
16
  <div class="container">
17
- <a class="navbar-brand" href="{{ url_for('dashboard.index') }}">Ad Analytics</a>
18
  <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
19
  <span class="navbar-toggler-icon"></span>
20
  </button>
21
  <div class="collapse navbar-collapse" id="navbarNav">
22
  <ul class="navbar-nav me-auto">
23
- <li class="nav-item">
24
- <a class="nav-link" href="{{ url_for('dashboard.index') }}">Dashboard</a>
25
- </li>
26
- <li class="nav-item">
27
- <a class="nav-link" href="{{ url_for('google_ads.index') }}">Google Ads</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
32
- </a>
33
- </li>
34
- <li class="nav-item">
35
- <a class="nav-link" href="{{ url_for('compliance.compliance_report') }}">
36
- <i class="fas fa-check-circle"></i> Compliance
37
- </a>
38
- </li>
 
39
  </ul>
40
  <ul class="navbar-nav">
41
- <li class="nav-item">
42
- <a class="nav-link" href="{{ url_for('auth.logout') }}">Logout</a>
43
- </li>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  </ul>
45
  </div>
46
  </div>
47
  </nav>
48
 
49
- <main>
 
 
 
 
 
 
 
 
 
 
50
  {% block content %}{% endblock %}
51
  </main>
52
 
53
- <footer class="bg-dark text-white text-center py-3 mt-5">
54
- <div class="container">
55
- <p class="mb-0">&copy; 2023 Ad Analytics Platform</p>
56
  </div>
57
  </footer>
58
 
59
- <!-- Bootstrap Bundle with Popper -->
60
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
61
- <!-- jQuery -->
62
- <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
63
  {% block scripts %}{% endblock %}
64
  </body>
65
  </html>
 
14
  <body>
15
  <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
16
  <div class="container">
17
+ <a class="navbar-brand" href="{{ url_for('index') }}">Ad Analysis Tool</a>
18
  <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
19
  <span class="navbar-toggler-icon"></span>
20
  </button>
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
32
+ </a>
33
+ </li>
34
+ <li class="nav-item">
35
+ <a class="nav-link" href="{{ url_for('compliance.compliance_report') }}">
36
+ <i class="fas fa-check-circle"></i> Compliance
37
+ </a>
38
+ </li>
39
+ {% endif %}
40
  </ul>
41
  <ul class="navbar-nav">
42
+ {% if current_user.is_authenticated %}
43
+ <li class="nav-item">
44
+ <span class="nav-link">
45
+ <i class="fas fa-user"></i> {{ current_user.email }}
46
+ </span>
47
+ </li>
48
+ <li class="nav-item">
49
+ <a class="nav-link" href="{{ url_for('auth.logout') }}">
50
+ <i class="fas fa-sign-out-alt"></i> Logout
51
+ </a>
52
+ </li>
53
+ {% else %}
54
+ <li class="nav-item">
55
+ <a class="nav-link" href="{{ url_for('auth.login') }}">
56
+ <i class="fas fa-sign-in-alt"></i> Login
57
+ </a>
58
+ </li>
59
+ <li class="nav-item">
60
+ <a class="nav-link" href="{{ url_for('auth.register') }}">
61
+ <i class="fas fa-user-plus"></i> Register
62
+ </a>
63
+ </li>
64
+ {% endif %}
65
  </ul>
66
  </div>
67
  </div>
68
  </nav>
69
 
70
+ <main class="container mt-4">
71
+ {% with messages = get_flashed_messages(with_categories=true) %}
72
+ {% if messages %}
73
+ {% for category, message in messages %}
74
+ <div class="alert alert-{{ category }}" role="alert">
75
+ {{ message }}
76
+ </div>
77
+ {% endfor %}
78
+ {% endif %}
79
+ {% endwith %}
80
+
81
  {% block content %}{% endblock %}
82
  </main>
83
 
84
+ <footer class="footer mt-5 py-3 bg-light">
85
+ <div class="container text-center">
86
+ <span class="text-muted" 2024 Ad Analysis Tool. All rights reserved.</span>
87
  </div>
88
  </footer>
89
 
90
+ <!-- Bootstrap JS -->
91
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
 
 
92
  {% block scripts %}{% endblock %}
93
  </body>
94
  </html>
app/templates/facebook_ads/ad_analysis.html CHANGED
@@ -1,6 +1,6 @@
1
  {% extends "base.html" %}
2
 
3
- {% block title %}Ad Analysis - {{ ad.advertiser }}{% endblock %}
4
 
5
  {% block content %}
6
  <div class="container mt-4">
@@ -13,6 +13,17 @@
13
  </ol>
14
  </nav>
15
 
 
 
 
 
 
 
 
 
 
 
 
16
  <div class="card mb-4">
17
  <div class="card-header">
18
  <h2 class="mb-0">Ad Analysis</h2>
@@ -50,7 +61,7 @@
50
  </p>
51
  {% else %}
52
  <div class="alert alert-info">
53
- Sentiment analysis is in progress...
54
  </div>
55
  {% endif %}
56
  </div>
@@ -72,7 +83,7 @@
72
  </small>
73
  {% else %}
74
  <div class="alert alert-info">
75
- Topic analysis is in progress...
76
  </div>
77
  {% endif %}
78
  </div>
@@ -109,12 +120,10 @@
109
  </small>
110
  </div>
111
  </div>
112
- {% endif %}
113
-
114
- {% if not ad.sentiment or not ad.topics %}
115
- <div class="alert alert-warning">
116
- <h4 class="alert-heading">Analysis in Progress</h4>
117
- <p>The ad content is being analyzed. This process may take a few moments. Please refresh the page to see updated results.</p>
118
  </div>
119
  {% endif %}
120
  </div>
 
1
  {% extends "base.html" %}
2
 
3
+ {% block title %}Ad Analysis{% endblock %}
4
 
5
  {% block content %}
6
  <div class="container mt-4">
 
13
  </ol>
14
  </nav>
15
 
16
+ {% if not ai_available %}
17
+ <div class="alert alert-warning" role="alert">
18
+ <h4 class="alert-heading">Limited Functionality</h4>
19
+ <p>This is a placeholder view. Full functionality requires installing the AI dependencies.</p>
20
+ <hr>
21
+ <p class="mb-0">To enable full functionality, please install the required dependencies:</p>
22
+ <code>pip install transformers torch textblob spacy</code><br>
23
+ <code>python -m spacy download en_core_web_sm</code>
24
+ </div>
25
+ {% endif %}
26
+
27
  <div class="card mb-4">
28
  <div class="card-header">
29
  <h2 class="mb-0">Ad Analysis</h2>
 
61
  </p>
62
  {% else %}
63
  <div class="alert alert-info">
64
+ Sentiment analysis is not available.
65
  </div>
66
  {% endif %}
67
  </div>
 
83
  </small>
84
  {% else %}
85
  <div class="alert alert-info">
86
+ Topic analysis is not available.
87
  </div>
88
  {% endif %}
89
  </div>
 
120
  </small>
121
  </div>
122
  </div>
123
+ {% else %}
124
+ <div class="alert alert-info">
125
+ <h4 class="alert-heading">Entity Analysis Not Available</h4>
126
+ <p>Named entity recognition requires the AI dependencies to be installed.</p>
 
 
127
  </div>
128
  {% endif %}
129
  </div>
app/templates/facebook_ads/ad_detail.html CHANGED
@@ -12,6 +12,17 @@
12
  </ol>
13
  </nav>
14
 
 
 
 
 
 
 
 
 
 
 
 
15
  <div class="card mb-4">
16
  <div class="card-header">
17
  <h2 class="mb-0">
@@ -117,7 +128,7 @@
117
  <div class="card-footer text-muted">
118
  <div class="row">
119
  <div class="col-md-6">
120
- Scraped: {{ ad.created_at.strftime('%Y-%m-%d %H:%M:%S') }}
121
  </div>
122
  <div class="col-md-6 text-end">
123
  Search Query: {{ ad.search_query or 'N/A' }}
 
12
  </ol>
13
  </nav>
14
 
15
+ {% if not ai_available %}
16
+ <div class="alert alert-warning" role="alert">
17
+ <h4 class="alert-heading">Limited Functionality</h4>
18
+ <p>This is a placeholder view. Full functionality requires installing the AI dependencies.</p>
19
+ <hr>
20
+ <p class="mb-0">To enable full functionality, please install the required dependencies:</p>
21
+ <code>pip install transformers torch textblob spacy</code><br>
22
+ <code>python -m spacy download en_core_web_sm</code>
23
+ </div>
24
+ {% endif %}
25
+
26
  <div class="card mb-4">
27
  <div class="card-header">
28
  <h2 class="mb-0">
 
128
  <div class="card-footer text-muted">
129
  <div class="row">
130
  <div class="col-md-6">
131
+ Scraped: {{ ad.created_at.strftime('%Y-%m-%d %H:%M:%S') if ad.created_at else 'N/A' }}
132
  </div>
133
  <div class="col-md-6 text-end">
134
  Search Query: {{ ad.search_query or 'N/A' }}
app/templates/facebook_ads/index.html CHANGED
@@ -6,6 +6,17 @@
6
  <div class="container mt-4">
7
  <h1 class="mb-4">Facebook Ads Dashboard</h1>
8
 
 
 
 
 
 
 
 
 
 
 
 
9
  <div class="row">
10
  <div class="col-md-6">
11
  <div class="card mb-4">
 
6
  <div class="container mt-4">
7
  <h1 class="mb-4">Facebook Ads Dashboard</h1>
8
 
9
+ {% if not ai_available %}
10
+ <div class="alert alert-warning" role="alert">
11
+ <h4 class="alert-heading">Limited Functionality</h4>
12
+ <p>Some features are currently disabled because the required AI dependencies are not installed.</p>
13
+ <hr>
14
+ <p class="mb-0">To enable full functionality, please install the required dependencies:</p>
15
+ <code>pip install transformers torch textblob spacy</code><br>
16
+ <code>python -m spacy download en_core_web_sm</code>
17
+ </div>
18
+ {% endif %}
19
+
20
  <div class="row">
21
  <div class="col-md-6">
22
  <div class="card mb-4">
migrations/versions/__pycache__/fd9168d1a5fa_update_facebook_ad_model.cpython-312.pyc ADDED
Binary file (4.1 kB). View file
 
migrations/versions/fd9168d1a5fa_update_facebook_ad_model.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Update facebook_ad model
2
+
3
+ Revision ID: fd9168d1a5fa
4
+ Revises: dddcd665398d
5
+ Create Date: 2025-03-10 09:11:24.460987
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+ from sqlalchemy.dialects import sqlite
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = 'fd9168d1a5fa'
14
+ down_revision = 'dddcd665398d'
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade():
20
+ # ### commands auto generated by Alembic - please adjust! ###
21
+ with op.batch_alter_table('facebook_ad', schema=None) as batch_op:
22
+ batch_op.drop_index('ix_facebook_ad_ad_id')
23
+ batch_op.drop_index('ix_facebook_ad_advertiser')
24
+ batch_op.drop_index('ix_facebook_ad_advertiser_id')
25
+ batch_op.drop_index('ix_facebook_ad_search_query')
26
+
27
+ op.drop_table('facebook_ad')
28
+ # ### end Alembic commands ###
29
+
30
+
31
+ def downgrade():
32
+ # ### commands auto generated by Alembic - please adjust! ###
33
+ op.create_table('facebook_ad',
34
+ sa.Column('id', sa.VARCHAR(length=36), nullable=False),
35
+ sa.Column('ad_id', sa.VARCHAR(length=255), nullable=True),
36
+ sa.Column('advertiser', sa.VARCHAR(length=255), nullable=True),
37
+ sa.Column('advertiser_id', sa.VARCHAR(length=255), nullable=True),
38
+ sa.Column('content', sa.TEXT(), nullable=True),
39
+ sa.Column('images', sqlite.JSON(), nullable=True),
40
+ sa.Column('links', sqlite.JSON(), nullable=True),
41
+ sa.Column('search_query', sa.VARCHAR(length=255), nullable=True),
42
+ sa.Column('position', sa.INTEGER(), nullable=True),
43
+ sa.Column('sentiment', sqlite.JSON(), nullable=True),
44
+ sa.Column('topics', sqlite.JSON(), nullable=True),
45
+ sa.Column('entities', sqlite.JSON(), nullable=True),
46
+ sa.Column('raw_data', sqlite.JSON(), nullable=True),
47
+ sa.Column('raw_text', sa.TEXT(), nullable=True),
48
+ sa.Column('created_at', sa.DATETIME(), nullable=True),
49
+ sa.Column('updated_at', sa.DATETIME(), nullable=True),
50
+ sa.Column('user_id', sa.INTEGER(), nullable=True),
51
+ sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
52
+ sa.PrimaryKeyConstraint('id')
53
+ )
54
+ with op.batch_alter_table('facebook_ad', schema=None) as batch_op:
55
+ batch_op.create_index('ix_facebook_ad_search_query', ['search_query'], unique=False)
56
+ batch_op.create_index('ix_facebook_ad_advertiser_id', ['advertiser_id'], unique=False)
57
+ batch_op.create_index('ix_facebook_ad_advertiser', ['advertiser'], unique=False)
58
+ batch_op.create_index('ix_facebook_ad_ad_id', ['ad_id'], unique=False)
59
+
60
+ # ### end Alembic commands ###