1010
1111import aiosqlite
1212
13- from project_forge .models import GenerationRun , Idea , IdeaCategory , IdeaStatus
13+ from project_forge .models import GenerationRun , Idea , IdeaCategory , IdeaStatus , Resource
1414
1515SCHEMA = """
1616CREATE TABLE IF NOT EXISTS ideas (
5353 used_at TEXT NOT NULL,
5454 PRIMARY KEY (category, concept_idx, domain_idx, direction)
5555);
56+
57+ CREATE TABLE IF NOT EXISTS resources (
58+ id TEXT PRIMARY KEY,
59+ domain TEXT NOT NULL UNIQUE,
60+ name TEXT NOT NULL,
61+ description TEXT NOT NULL,
62+ url TEXT,
63+ categories TEXT NOT NULL DEFAULT '[]',
64+ idea_count INTEGER NOT NULL DEFAULT 0,
65+ added_at TEXT NOT NULL
66+ );
5667"""
5768
5869
@@ -74,6 +85,11 @@ async def connect(self):
7485 await self ._db .execute ("ALTER TABLE ideas ADD COLUMN content_hash TEXT" )
7586 except Exception : # noqa: S110
7687 pass # Column already exists on migrated DBs
88+ # Migration: add source_url column if missing
89+ try :
90+ await self ._db .execute ("ALTER TABLE ideas ADD COLUMN source_url TEXT" )
91+ except Exception : # noqa: S110
92+ pass # Column already exists on migrated DBs
7793 # Add indexes (safe to re-run)
7894 await self ._db .execute (
7995 "CREATE UNIQUE INDEX IF NOT EXISTS idx_ideas_content_hash "
@@ -107,8 +123,8 @@ async def save_idea(self, idea: Idea) -> Idea:
107123 """INSERT OR REPLACE INTO ideas
108124 (id, name, tagline, description, category, market_analysis,
109125 feasibility_score, mvp_scope, tech_stack, generated_at, status,
110- github_issue_url, project_repo_url, content_hash)
111- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""" ,
126+ github_issue_url, project_repo_url, content_hash, source_url )
127+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )""" ,
112128 (
113129 idea .id ,
114130 idea .name ,
@@ -124,6 +140,7 @@ async def save_idea(self, idea: Idea) -> Idea:
124140 idea .github_issue_url ,
125141 idea .project_repo_url ,
126142 content_hash ,
143+ idea .source_url ,
127144 ),
128145 )
129146 await self .db .commit ()
@@ -300,6 +317,7 @@ async def get_stats(self) -> dict:
300317
301318 @staticmethod
302319 def _row_to_idea (row ) -> Idea :
320+ keys = row .keys () if hasattr (row , "keys" ) else []
303321 return Idea (
304322 id = row ["id" ],
305323 name = row ["name" ],
@@ -316,4 +334,67 @@ def _row_to_idea(row) -> Idea:
316334 status = row ["status" ],
317335 github_issue_url = row ["github_issue_url" ],
318336 project_repo_url = row ["project_repo_url" ],
337+ source_url = row ["source_url" ] if "source_url" in keys else None ,
338+ )
339+
340+ # === RESOURCE CRUD ===
341+
342+ async def save_resource (self , resource : Resource ) -> Resource :
343+ await self .db .execute (
344+ """INSERT OR REPLACE INTO resources
345+ (id, domain, name, description, url, categories, idea_count, added_at)
346+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)""" ,
347+ (
348+ resource .id ,
349+ resource .domain ,
350+ resource .name ,
351+ resource .description ,
352+ resource .url ,
353+ json .dumps (resource .categories ),
354+ resource .idea_count ,
355+ resource .added_at .isoformat (),
356+ ),
357+ )
358+ await self .db .commit ()
359+ return resource
360+
361+ async def get_resource (self , resource_id : str ) -> Resource | None :
362+ cursor = await self .db .execute ("SELECT * FROM resources WHERE id = ?" , (resource_id ,))
363+ row = await cursor .fetchone ()
364+ if not row :
365+ return None
366+ return self ._row_to_resource (row )
367+
368+ async def get_resource_by_domain (self , domain : str ) -> Resource | None :
369+ cursor = await self .db .execute ("SELECT * FROM resources WHERE domain = ?" , (domain ,))
370+ row = await cursor .fetchone ()
371+ if not row :
372+ return None
373+ return self ._row_to_resource (row )
374+
375+ async def list_resources (self ) -> list [Resource ]:
376+ cursor = await self .db .execute ("SELECT * FROM resources ORDER BY added_at DESC" )
377+ rows = await cursor .fetchall ()
378+ return [self ._row_to_resource (row ) for row in rows ]
379+
380+ async def increment_resource_idea_count (self , domain : str ) -> None :
381+ await self .db .execute (
382+ "UPDATE resources SET idea_count = idea_count + 1 WHERE domain = ?" ,
383+ (domain ,),
384+ )
385+ await self .db .commit ()
386+
387+ @staticmethod
388+ def _row_to_resource (row ) -> Resource :
389+ return Resource (
390+ id = row ["id" ],
391+ domain = row ["domain" ],
392+ name = row ["name" ],
393+ description = row ["description" ],
394+ url = row ["url" ],
395+ categories = json .loads (row ["categories" ]),
396+ idea_count = row ["idea_count" ],
397+ added_at = datetime .fromisoformat (row ["added_at" ]).replace (tzinfo = UTC )
398+ if "+" not in row ["added_at" ]
399+ else datetime .fromisoformat (row ["added_at" ]),
319400 )
0 commit comments