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),
|
(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:
|
def update_campaign(self, campaign_id: int, **fields) -> dict | None:
|
||||||
allowed = {"name", "product", "cron_schedule", "active", "notes"}
|
allowed = {"name", "product", "cron_schedule", "active", "notes"}
|
||||||
updates = {k: v for k, v in fields.items() if k in allowed}
|
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),
|
(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:
|
def update_variant(self, variant_id: int, **fields) -> dict | None:
|
||||||
allowed = {"sub_pattern", "title", "body", "flair", "notes"}
|
allowed = {"sub_pattern", "title", "body", "flair", "notes"}
|
||||||
updates = {k: v for k, v in fields.items() if k in allowed}
|
updates = {k: v for k, v in fields.items() if k in allowed}
|
||||||
|
|
@ -187,6 +233,32 @@ class Store:
|
||||||
(campaign_id, sub, sort_order),
|
(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:
|
def remove_campaign_sub(self, campaign_id: int, sub: str) -> bool:
|
||||||
cur = self.conn.execute(
|
cur = self.conn.execute(
|
||||||
"DELETE FROM campaign_subs WHERE campaign_id = ? AND sub = ?", (campaign_id, sub)
|
"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)
|
store.create_variant(campaign_id=cid, **v)
|
||||||
print(f" variant: {v['sub_pattern']!r}")
|
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()
|
||||||
print("Seeding sub rules...")
|
print("Seeding sub rules...")
|
||||||
for rule in SUB_RULES:
|
for rule in SUB_RULES:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue