diff --git a/app/db/store.py b/app/db/store.py index 0f2a0dd..5de8b6c 100644 --- a/app/db/store.py +++ b/app/db/store.py @@ -97,6 +97,27 @@ class Store: (name, product, platform, cron_schedule, notes, type), ) + def get_or_create_campaign( + self, + name: str, + product: str, + platform: str = "reddit", + type: str = "reddit_post", + cron_schedule: str | None = None, + ) -> dict: + existing = self._fetchone( + "SELECT * FROM campaigns WHERE name = ? AND product = ?", (name, product) + ) + if existing: + return dict(existing) + return self.create_campaign( + name=name, + product=product, + platform=platform, + type=type, + cron_schedule=cron_schedule, + ) + def update_campaign(self, campaign_id: int, **fields) -> dict | None: allowed = {"name", "product", "cron_schedule", "active", "notes"} updates = {k: v for k, v in fields.items() if k in allowed} @@ -152,6 +173,31 @@ class Store: (campaign_id, sub_pattern, title, body, flair, notes), ) + def upsert_variant( + self, + campaign_id: int, + sub_pattern: str, + title: str, + body: str, + flair: str | None = None, + ) -> dict: + existing = self._fetchone( + "SELECT * FROM campaign_variants WHERE campaign_id = ? AND sub_pattern = ?", + (campaign_id, sub_pattern), + ) + if existing: + self.conn.execute( + "UPDATE campaign_variants SET title=?, body=?, flair=? WHERE id=?", + (title, body, flair, existing["id"]), + ) + self.conn.commit() + return self._fetchone("SELECT * FROM campaign_variants WHERE id=?", (existing["id"],)) + return self._insert_returning( + "INSERT INTO campaign_variants (campaign_id, sub_pattern, title, body, flair)" + " VALUES (?,?,?,?,?) RETURNING *", + (campaign_id, sub_pattern, title, body, flair), + ) + def update_variant(self, variant_id: int, **fields) -> dict | None: allowed = {"sub_pattern", "title", "body", "flair", "notes"} updates = {k: v for k, v in fields.items() if k in allowed} @@ -187,6 +233,32 @@ class Store: (campaign_id, sub, sort_order), ) + def upsert_campaign_sub( + self, + campaign_id: int, + sub: str, + sort_order: int = 0, + thread_title_pattern: str | None = None, + thread_url_override: str | None = None, + occurrence: str | None = None, + ) -> dict: + self.conn.execute( + """INSERT INTO campaign_subs + (campaign_id, sub, sort_order, thread_title_pattern, thread_url_override, occurrence) + VALUES (?, ?, ?, ?, ?, ?) + ON CONFLICT(campaign_id, sub) DO UPDATE SET + sort_order = excluded.sort_order, + thread_title_pattern = excluded.thread_title_pattern, + thread_url_override = excluded.thread_url_override, + occurrence = excluded.occurrence""", + (campaign_id, sub, sort_order, thread_title_pattern, thread_url_override, occurrence), + ) + self.conn.commit() + return self._fetchone( + "SELECT * FROM campaign_subs WHERE campaign_id = ? AND sub = ?", + (campaign_id, sub), + ) + def remove_campaign_sub(self, campaign_id: int, sub: str) -> bool: cur = self.conn.execute( "DELETE FROM campaign_subs WHERE campaign_id = ? AND sub = ?", (campaign_id, sub) diff --git a/scripts/seed_campaigns.py b/scripts/seed_campaigns.py index 2b27f97..16c35fd 100644 --- a/scripts/seed_campaigns.py +++ b/scripts/seed_campaigns.py @@ -342,6 +342,73 @@ def seed(store: Store) -> None: store.create_variant(campaign_id=cid, **v) print(f" variant: {v['sub_pattern']!r}") + # --- r/Flipping Sunday self-promo (Snipe) --- + flipping_campaign = store.get_or_create_campaign( + name="Snipe | Sunday self-promo — r/Flipping", + product="snipe", + platform="reddit", + type="reddit_comment", + cron_schedule="0 16 * * 0", # every Sunday 16:00 UTC + ) + flipping_status = "skip" if flipping_campaign["name"] in existing_names else "+" + print(f" [{flipping_status}] campaign {flipping_campaign['id']}: {flipping_campaign['name']!r}") + store.upsert_campaign_sub( + campaign_id=flipping_campaign["id"], + sub="Flipping", + sort_order=0, + thread_title_pattern="Weekly Self-Promotion", + occurrence="every", + ) + print(" sub: r/Flipping (thread_title_pattern='Weekly Self-Promotion', occurrence='every')") + store.upsert_variant( + campaign_id=flipping_campaign["id"], + sub_pattern="*", + title="", + body=( + "Working on evaluating auction listings? I built **Snipe** — " + "a trust-scoring tool for eBay and estate auction platforms. " + "It checks seller history, flags marketing photos, and scores " + "listings before you bid.\n\n" + "Still in beta, free to try: https://circuitforge.tech\n\n" + "Happy to answer questions about how it works." + ), + ) + print(" variant: '*'") + + # --- r/cscareerquestions first-Sunday megathread (Peregrine) --- + cscq_campaign = store.get_or_create_campaign( + name="Peregrine | First-Sunday megathread — r/cscareerquestions", + product="peregrine", + platform="reddit", + type="reddit_comment", + cron_schedule="0 16 * * 0", # every Sunday 16:00 UTC; occurrence gates to first_sunday + ) + cscq_status = "skip" if cscq_campaign["name"] in existing_names else "+" + print(f" [{cscq_status}] campaign {cscq_campaign['id']}: {cscq_campaign['name']!r}") + store.upsert_campaign_sub( + campaign_id=cscq_campaign["id"], + sub="cscareerquestions", + sort_order=0, + thread_title_pattern="Monthly Resume", + occurrence="first_sunday", + ) + print(" sub: r/cscareerquestions (thread_title_pattern='Monthly Resume', occurrence='first_sunday')") + store.upsert_variant( + campaign_id=cscq_campaign["id"], + sub_pattern="*", + title="", + body=( + "I'm building **Peregrine** — a local-first job search assistant " + "for neurodivergent and adaptive-needs folks. It helps with " + "cover letters, interview prep, and tracking applications without " + "your data leaving your machine.\n\n" + "Free tier available: https://circuitforge.tech/peregrine\n\n" + "Built by someone who's been through the grind — genuinely trying " + "to make this less awful." + ), + ) + print(" variant: '*'") + print() print("Seeding sub rules...") for rule in SUB_RULES: