Skip to content

Commit 8b57464

Browse files
committed
added tests
1 parent 66617e2 commit 8b57464

File tree

17 files changed

+489
-207
lines changed

17 files changed

+489
-207
lines changed

app.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ def get_companies():
3636

3737
techteams = app.db.get_publishers_by_type(conn, publisher_type="techteam")
3838
teamNames = [team["publisher_name"] for team in techteams if query in team["publisher_name"].lower()]
39+
teamNames.sort()
40+
conn.close()
3941
return jsonify(teamNames)
4042

4143
@app.route('/subscribe', methods=['POST'])
@@ -102,6 +104,7 @@ def subscriptions_for_email():
102104

103105
# Convert sets to lists for JSON serializability
104106
result = {topic: list(publishers) for topic, publishers in grouped.items()}
107+
conn.close()
105108
return jsonify(result)
106109

107110
@app.route("/interested", methods=["POST"])

classifier.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
enums.PublisherCategory.PRODUCT_MANAGEMENT.value: (
2323
"product strategy, design, UX/UI, customer research, roadmap planning, prioritization, market analysis, cross-functional collaboration, agile, lean, product launch, product lifecycle management, product metrics, stakeholder communication"
2424
),
25-
"GENERAL": (
25+
enums.PublisherCategory.GENERAL.value: (
2626
"general technology, business, professional development, industry news, opinions, AI and tech trends, interdisciplinary topics, updates not fitting other categories"
2727
)
2828
}
@@ -89,6 +89,6 @@ def classify_post(post_title, tags="", content=""):
8989
top_score = sorted_scores[0]
9090
second_score = sorted_scores[1] if len(sorted_scores) > 1 else 0.0
9191
if top_score < 0.25 or (top_score - second_score) < 0.05:
92-
return "GENERAL"
92+
return enums.PublisherCategory.GENERAL.value
9393

9494
return best_cat

db/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def remove_subscription(self, email, topic, publisher_id):
2626
pass
2727

2828
@abstractmethod
29-
def get_notifications(self):
29+
def get_active_notifications(self):
3030
pass
3131

3232
@abstractmethod

db/enums.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ class PublisherCategory(Enum):
1010
DATA_ANALYTICS = "Data Analytics"
1111
DATA_SCIENCE = "Data Science"
1212
SOFTWARE_TESTING = "Software Testing"
13-
PRODUCT_MANAGEMENT = "Product Management"
13+
PRODUCT_MANAGEMENT = "Product Management"
14+
GENERAL = "General"

db/sqlite.py

Lines changed: 164 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,32 @@ class SQLiteDatabase:
1111

1212
def __init__(self, db_path):
1313
self.db_path = db_path
14-
self.conn = None
15-
self._connect()
1614
conn = self.get_connection()
1715
c = conn.cursor()
1816

1917
# Ensure tables
2018
c.execute("""
2119
CREATE TABLE IF NOT EXISTS notifications (
2220
id INTEGER PRIMARY KEY AUTOINCREMENT,
23-
email TEXT,
24-
heading TEXT,
21+
email TEXT NOT NULL,
22+
heading TEXT NOT NULL,
23+
post_url TEXT NOT NULL,
24+
post_title TEXT NOT NULL,
2525
style_version INTEGER,
26-
post_url TEXT,
27-
post_title TEXT,
28-
deleted BOOL DEFAULT 0
26+
deleted BOOL DEFAULT 0,
27+
maturity_date DATETIME NOT NULL
2928
)
3029
""")
3130
c.execute("""
3231
CREATE TABLE IF NOT EXISTS subscriptions (
3332
id INTEGER PRIMARY KEY AUTOINCREMENT,
34-
email TEXT,
35-
publisher_id INTEGER,
33+
email TEXT NOT NULL,
34+
publisher_id INTEGER NOT NULL,
3635
joined_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
3736
topic TEXT NOT NULL CHECK (topic IN ('Software Engineering', 'Data Analytics', 'Data Science', 'Software Testing', 'Product Management')),
37+
frequency_in_days INTEGER DEFAULT 3,
3838
last_notified_at DATETIME DEFAULT NULL,
39+
active BOOL DEFAULT 1,
3940
FOREIGN KEY (publisher_id) REFERENCES publishers(id),
4041
UNIQUE (email, publisher_id, topic)
4142
)
@@ -49,49 +50,54 @@ def __init__(self, db_path):
4950
UNIQUE (publisher_name)
5051
)
5152
""")
53+
c.execute("""
54+
CREATE TABLE IF NOT EXISTS posts(
55+
id INTEGER PRIMARY KEY AUTOINCREMENT,
56+
publisher_id INTEGER NOT NULL,
57+
url TEXT NOT NULL,
58+
title TEXT NOT NULL,
59+
tags TEXT,
60+
published_at DATETIME NOT NULL,
61+
modified_at DATETIME NOT NULL,
62+
labelled BOOL DEFAULT 0,
63+
topic TEXT NOT NULL CHECK (topic IN ('Software Engineering', 'Data Analytics', 'Data Science', 'Software Testing', 'Product Management', 'General')),
64+
FOREIGN KEY (publisher_id) REFERENCES publishers(id),
65+
UNIQUE (url)
66+
)
67+
""")
5268
logger.info(f"SQLite database initialized Successfully")
5369
conn.commit()
54-
55-
def _connect(self):
56-
if self.conn:
57-
try:
58-
self.conn.execute("SELECT 1")
59-
return # connection is still alive
60-
except Exception:
61-
self.close()
62-
63-
self.conn = sqlite3.connect(self.db_path, check_same_thread=False)
64-
self.conn.row_factory = sqlite3.Row
65-
logger.info("Connection Initialized")
70+
conn.close()
6671

6772
def get_connection(self):
68-
self._connect()
69-
return self.conn
70-
71-
def close(self):
72-
if self.conn:
73-
try:
74-
self.conn.close()
75-
except Exception:
76-
pass
77-
self.conn = None
73+
"""
74+
Always returns a new SQLite connection.
75+
Caller is responsible for closing it after use.
76+
"""
77+
conn = sqlite3.connect(self.db_path, check_same_thread=False)
78+
conn.row_factory = sqlite3.Row
79+
logger.info("New SQLite connection created")
80+
return conn
7881

7982
def get_subscriptions(self, conn):
8083
c = conn.cursor()
8184
c.execute("""
8285
SELECT s.email, s.publisher_id, s.joined_time, s.last_notified_at,
83-
p.id as publisher_id, p.publisher_name, p.last_scraped_at, p.publisher_type
86+
p.id as publisher_id, p.publisher_name, p.last_scraped_at, p.publisher_type, s.topic, s.frequency_in_days
8487
FROM subscriptions s
8588
JOIN publishers p ON s.publisher_id = p.id
89+
WHERE s.active=1
8690
""")
8791
rows = c.fetchall()
8892
result = []
8993
for row in rows:
9094
subscription = {
9195
"email": row["email"],
96+
'topic': row['topic'],
9297
"publisher_id": row["publisher_id"],
9398
"joined_time": row["joined_time"],
9499
"last_notified_at": row["last_notified_at"],
100+
'frequency_in_days': row['frequency_in_days'],
95101
"publisher": {
96102
"id": row["publisher_id"],
97103
"publisher_name": row["publisher_name"],
@@ -109,7 +115,7 @@ def get_subscriptions_by_email(self, conn, email):
109115
FROM (
110116
SELECT *
111117
FROM subscriptions
112-
WHERE email = ?
118+
WHERE email = ? and active = 1
113119
) s
114120
JOIN publishers p ON s.publisher_id = p.id
115121
"""
@@ -132,51 +138,80 @@ def get_subscriptions_by_email(self, conn, email):
132138
for row in cursor.fetchall()
133139
]
134140

141+
def get_subscriptions_by_email_and_topic_and_publisher_id(self, conn, email, topic, publisher_id):
142+
query = """
143+
SELECT s.email, s.topic, s.publisher_id, s.joined_time, s.last_notified_at,
144+
p.id AS publisher_id, p.publisher_name, p.last_scraped_at, p.publisher_type
145+
FROM subscriptions s
146+
JOIN publishers p ON s.publisher_id = p.id
147+
WHERE s.email = ? and s.topic = ? and s.active = 1 and p.id = ?
148+
"""
149+
cursor = conn.execute(query, (email, topic, publisher_id))
150+
row = cursor.fetchone()
151+
if row:
152+
return dict(row)
153+
else:
154+
return None
155+
135156
def get_subscriptions_by_publisher(self, conn, publisher_id):
136157
c = conn.cursor()
137158
c.execute("""
138-
SELECT s.email, s.topic, s.joined_time, s.last_notified_at
159+
SELECT *
139160
FROM subscriptions s
140161
JOIN publishers p ON s.publisher_id = p.id
141-
WHERE p.id = ?
162+
WHERE p.id = ? and s.active=1
142163
""", (publisher_id,))
143164
rows = c.fetchall()
144-
return [
145-
{
146-
"email": row["email"],
147-
"topic": row["topic"],
148-
"joined_time": row["joined_time"],
149-
"last_notified_at": row["last_notified_at"]
150-
}
151-
for row in rows
152-
]
165+
return [dict(row) for row in rows]
153166

154-
def add_subscription(self, conn, email, topic, publisher_id, joined_time=None):
167+
def add_subscription(self, conn, email, topic, publisher_id, joined_time=None, operation=""):
155168
c = conn.cursor()
156-
logger.info(f"Adding subscription for email: {email}, topic: {topic}, publisher_id: {publisher_id}, joined_time: {joined_time}")
169+
logger.info(f"Adding subscription for email: {email}, topic: {topic}, publisher_id: {publisher_id}, joined_time: {joined_time}")
170+
171+
sub = self.get_subscriptions_by_email_and_topic_and_publisher_id(conn, email, topic, publisher_id);
157172

158-
if joined_time is None:
159-
# Let SQLite fill in the default CURRENT_TIMESTAMP
173+
if operation == "resume":
160174
c.execute("""
161-
INSERT INTO subscriptions (email, topic, publisher_id)
162-
VALUES (?, ?, ?)
163-
""", (email, topic, publisher_id))
175+
UPDATE subscriptions
176+
SET last_notified_at = CURRENT_TIMESTAMP, active = 1
177+
WHERE id = ?
178+
""", (sub['id'],))
179+
180+
logger.info(f"Subscription for {email} resumed successfully")
181+
182+
return sub['id']
183+
184+
elif not sub:
185+
if joined_time is None:
186+
# Let SQLite fill in the default CURRENT_TIMESTAMP
187+
c.execute("""
188+
INSERT INTO subscriptions (email, topic, publisher_id)
189+
VALUES (?, ?, ?)
190+
""", (email, topic, publisher_id))
191+
else:
192+
c.execute("""
193+
INSERT INTO subscriptions (email, topic, publisher_id, joined_time)
194+
VALUES (?, ?, ?, ?)
195+
""", (email, topic, publisher_id, joined_time))
196+
197+
logger.info(f"Subscription for {email} addeed successfully")
198+
199+
return c.lastrowid
164200
else:
165-
c.execute("""
166-
INSERT INTO subscriptions (email, topic, publisher_id, joined_time)
167-
VALUES (?, ?, ?, ?)
168-
""", (email, topic, publisher_id, joined_time))
201+
logger.info(f"Subscription for {email} already exists")
202+
return sub['id']
169203

170-
logger.info(f"Subscription for {email} added successfully")
204+
171205

172-
def remove_subscription(self, conn, email, topic, publisher_id):
206+
def remove_subscription(self, conn, id):
173207
c = conn.cursor()
174208
c.execute("""
175-
DELETE FROM subscriptions
176-
WHERE email = ? AND topic = ? AND publisher_id = ?
177-
""", (email, topic, publisher_id))
209+
UPDATE subscriptions
210+
SET active=0
211+
WHERE id = ?
212+
""", (id,))
178213

179-
def get_notifications(self, conn):
214+
def get_active_notifications(self, conn):
180215
c = conn.cursor()
181216
c.execute("""
182217
SELECT *
@@ -196,23 +231,23 @@ def get_notifications_by_email(self, conn, email):
196231
rows = c.fetchall()
197232
return [dict(row) for row in rows]
198233

199-
def add_notification(self, conn, email, heading, style_version, post_url, post_title):
234+
def add_notification(self, conn, email, heading, style_version, post_url, post_title, maturity_date):
200235
logger.info(f"Adding notification: {email}, type: {post_title}")
201236
c = conn.cursor()
202237
c.execute("""
203-
INSERT INTO notifications (email, heading, style_version, post_url, post_title)
204-
VALUES (?, ?, ?, ?, ?)
205-
""", (email, heading, style_version, post_url, post_title))
238+
INSERT INTO notifications (email, heading, style_version, post_url, post_title, maturity_date)
239+
VALUES (?, ?, ?, ?, ?, ?)
240+
""", (email, heading, style_version, post_url, post_title, maturity_date))
206241
logger.info("notification added successfully!")
207242

208-
def delete_notification(self, conn, email, heading, post_title):
209-
logger.info(f"Deleting notification: {email}, type: {post_title}")
243+
def delete_notification(self, conn, email, post_url):
244+
logger.info(f"Deleting notification: {email}, url: {post_url}")
210245
c = conn.cursor()
211246
c.execute("""
212247
UPDATE notifications
213248
SET deleted = 1
214-
WHERE email = ? AND heading = ? AND post_title = ?
215-
""", (email, heading, post_title))
249+
WHERE email = ? AND post_url = ?
250+
""", (email, post_url))
216251
logger.info("notification deleted successfully!")
217252

218253
def delete_notifications_by_email(self, conn, email):
@@ -272,6 +307,66 @@ def update_publisher(self, conn, publisher_id, last_scraped_at):
272307
WHERE id = ?
273308
""", (last_scraped_at, publisher_id))
274309
logger.info(f"Publisher {publisher_id} updated successfully")
310+
311+
def add_post(self, conn, post_url, post_title, published_by, tags, published_at, topic):
312+
logger.info(f"Adding post: {post_title}, published_by: {published_by}")
313+
c = conn.cursor()
314+
315+
post = self.get_post_by_url(conn, post_url)
316+
317+
if not post:
318+
c.execute("""
319+
INSERT INTO posts (url, title, publisher_id, topic, tags, published_at, modified_at)
320+
VALUES (?, ?, ?, ?, ?, ?, ?)
321+
""", (post_url, post_title, published_by, topic, tags, published_at, published_at))
322+
logger.info(f"post {post_title} added successfully!")
323+
return c.lastrowid
324+
else:
325+
logger.info(f"post {post_title} already exists!")
326+
return post['id']
327+
328+
329+
def get_post_by_url(self, conn, url):
330+
c = conn.cursor()
331+
c.execute("""
332+
SELECT *
333+
FROM posts
334+
WHERE url = ?
335+
""", (url,))
336+
row = c.fetchone()
337+
return dict(row) if row else None
338+
339+
def get_labelled_post_by_publisher_and_topic(self,conn, publisher_id, topic):
340+
c = conn.cursor()
341+
c.execute("""
342+
SELECT *
343+
FROM posts
344+
WHERE publisher_id = ? AND topic = ? AND labelled=1
345+
""", (publisher_id, topic))
346+
rows = c.fetchall()
347+
return [dict(row) for row in rows]
348+
349+
def get_posts(self, conn):
350+
c = conn.cursor()
351+
c.execute("""
352+
SELECT *
353+
FROM posts
354+
""")
355+
rows = c.fetchall()
356+
return [dict(row) for row in rows]
357+
358+
def update_post_label(self, conn, post_id, label):
359+
logger.info(f"Updating post: {post_id}, with label: {label}")
360+
c = conn.cursor()
361+
c.execute("""
362+
UPDATE posts
363+
SET topic = ?,
364+
modified_at = CURRENT_TIMESTAMP,
365+
labelled = 1
366+
WHERE id = ?
367+
""", (label, post_id))
368+
conn.commit() # don’t forget to commit the change
369+
logger.info(f"Post {post_id} updated successfully")
275370

276371
@classmethod
277372
def get_instance(cls, db_path):

handlers/facebook.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def search_blog_posts(self, category, last_scan_time):
105105
posts.append({
106106
"title": title,
107107
"url": post_url,
108-
"categories": cats,
108+
"tags": cats,
109109
"published": published.isoformat()
110110
})
111111

0 commit comments

Comments
 (0)