feat: add store helpers and seed r/Flipping + r/cscareerquestions comment campaigns
This commit is contained in:
parent
9248410cf1
commit
a06582c028
2 changed files with 139 additions and 0 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in a new issue