@@ -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 ):
0 commit comments