social-autoposter 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/syncfield.sh ADDED
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env bash
2
+ # syncfield.sh — Sync SQLite → Neon Postgres (idempotent upsert)
3
+ # Called after git push in stats.sh and engage.sh
4
+ # Requires: sqlite3, psql, DATABASE_URL in .env
5
+
6
+ set -euo pipefail
7
+
8
+ DB="$HOME/social-autoposter/social_posts.db"
9
+
10
+ # Load secrets
11
+ # shellcheck source=/dev/null
12
+ [ -f "$HOME/social-autoposter/.env" ] && source "$HOME/social-autoposter/.env"
13
+
14
+ if [ -z "${DATABASE_URL:-}" ]; then
15
+ echo "syncfield: DATABASE_URL not set, skipping sync"
16
+ exit 0
17
+ fi
18
+
19
+ TMPDIR="${TMPDIR:-/tmp}"
20
+
21
+ sync_table() {
22
+ local table="$1"
23
+ local columns="$2"
24
+ local conflict_col="${3:-id}"
25
+ local csv_file="$TMPDIR/syncfield_${table}.csv"
26
+
27
+ # Export from SQLite as CSV
28
+ sqlite3 -header -csv "$DB" "SELECT $columns FROM $table;" > "$csv_file"
29
+
30
+ local row_count
31
+ row_count=$(wc -l < "$csv_file" | tr -d ' ')
32
+ row_count=$((row_count - 1)) # subtract header
33
+
34
+ if [ "$row_count" -le 0 ]; then
35
+ rm -f "$csv_file"
36
+ return
37
+ fi
38
+
39
+ # Build column list for SET clause (exclude conflict column)
40
+ local set_clause=""
41
+ IFS=',' read -ra cols <<< "$columns"
42
+ for col in "${cols[@]}"; do
43
+ col=$(echo "$col" | tr -d ' ')
44
+ if [ "$col" != "$conflict_col" ]; then
45
+ if [ -n "$set_clause" ]; then
46
+ set_clause="$set_clause, "
47
+ fi
48
+ set_clause="${set_clause}${col} = EXCLUDED.${col}"
49
+ fi
50
+ done
51
+
52
+ # Upsert via temp table + INSERT ON CONFLICT
53
+ psql "$DATABASE_URL" -q <<SQL
54
+ CREATE TEMP TABLE _tmp_${table} (LIKE ${table} INCLUDING ALL);
55
+ \\copy _tmp_${table}($columns) FROM '$csv_file' WITH (FORMAT csv, HEADER true, NULL '');
56
+ INSERT INTO ${table}($columns)
57
+ SELECT $columns FROM _tmp_${table}
58
+ ON CONFLICT ($conflict_col) DO UPDATE SET $set_clause;
59
+ DROP TABLE _tmp_${table};
60
+ SQL
61
+
62
+ echo "syncfield: synced $table ($row_count rows)"
63
+ rm -f "$csv_file"
64
+ }
65
+
66
+ # Sync each table
67
+ sync_table "posts" "id,platform,thread_url,thread_author,thread_author_handle,thread_title,thread_content,thread_engagement,our_url,our_content,our_account,posted_at,discovered_at,status,status_checked_at,engagement_updated_at,upvotes,comments_count,views,source_turn_id,source_summary,top_comment_author,top_comment_content,top_comment_upvotes,top_comment_url"
68
+
69
+ sync_table "campaigns" "id,name,prompt,platforms,status,max_posts_per_day,posts_made,created_at,updated_at"
70
+
71
+ sync_table "replies" "id,post_id,platform,their_comment_id,their_author,their_content,their_comment_url,our_reply_id,our_reply_content,our_reply_url,parent_reply_id,moltbook_post_uuid,moltbook_parent_comment_uuid,depth,status,skip_reason,discovered_at,replied_at"
72
+
73
+ sync_table "thread_comments" "id,thread_id,author,author_handle,content,engagement,discovered_at"
74
+
75
+ # Update sync timestamp
76
+ psql "$DATABASE_URL" -q -c "INSERT INTO _syncfield_meta (key, value) VALUES ('last_sync', NOW()::text) ON CONFLICT (key) DO UPDATE SET value = NOW()::text;"
77
+
78
+ echo "syncfield: sync complete at $(date '+%Y-%m-%d %H:%M:%S')"