Switch from PostgreSQL to SQLite (modernc.org/sqlite, pure Go) for simpler deployment on the MCP platform. Fix URL normalization to preserve query parameters so sites like YouTube deduplicate correctly. Add Dockerfile, Makefile, and MCP service definition. Add pg2sqlite migration tool. Support $PORT env var for MCP port assignment. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
95 lines
2.1 KiB
Go
95 lines
2.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v4"
|
|
_ "modernc.org/sqlite"
|
|
)
|
|
|
|
func main() {
|
|
pgConn := flag.String("pg", "", "postgres connection string (e.g. postgres://user:pass@host:5432/kls?sslmode=verify-full)")
|
|
out := flag.String("out", "kls.db", "output sqlite file path")
|
|
flag.Parse()
|
|
|
|
if *pgConn == "" {
|
|
fmt.Fprintln(os.Stderr, "usage: pg2sqlite -pg <postgres-url> [-out kls.db]")
|
|
os.Exit(1)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
// Connect to Postgres
|
|
pg, err := pgx.Connect(ctx, *pgConn)
|
|
if err != nil {
|
|
log.Fatalf("postgres: %v", err)
|
|
}
|
|
defer pg.Close(ctx)
|
|
|
|
// Open SQLite
|
|
os.Remove(*out)
|
|
lite, err := sql.Open("sqlite", *out)
|
|
if err != nil {
|
|
log.Fatalf("sqlite open: %v", err)
|
|
}
|
|
defer lite.Close()
|
|
|
|
_, err = lite.ExecContext(ctx, `CREATE TABLE urls (
|
|
id TEXT PRIMARY KEY,
|
|
url TEXT NOT NULL,
|
|
nurl TEXT NOT NULL,
|
|
short TEXT NOT NULL UNIQUE,
|
|
created_at TEXT NOT NULL
|
|
)`)
|
|
if err != nil {
|
|
log.Fatalf("sqlite create table: %v", err)
|
|
}
|
|
|
|
// Read from Postgres
|
|
rows, err := pg.Query(ctx, `SELECT id, url, nurl, short, created_at FROM urls ORDER BY created_at`)
|
|
if err != nil {
|
|
log.Fatalf("postgres query: %v", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
tx, err := lite.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
log.Fatalf("sqlite begin: %v", err)
|
|
}
|
|
|
|
stmt, err := tx.PrepareContext(ctx, `INSERT INTO urls (id, url, nurl, short, created_at) VALUES (?, ?, ?, ?, ?)`)
|
|
if err != nil {
|
|
log.Fatalf("sqlite prepare: %v", err)
|
|
}
|
|
defer stmt.Close()
|
|
|
|
var n int
|
|
for rows.Next() {
|
|
var id, url, nurl, short string
|
|
var createdAt time.Time
|
|
if err := rows.Scan(&id, &url, &nurl, &short, &createdAt); err != nil {
|
|
log.Fatalf("postgres scan: %v", err)
|
|
}
|
|
|
|
if _, err := stmt.ExecContext(ctx, id, url, nurl, short, createdAt.Format(time.RFC3339)); err != nil {
|
|
log.Fatalf("sqlite insert: %v", err)
|
|
}
|
|
n++
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
log.Fatalf("postgres rows: %v", err)
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
log.Fatalf("sqlite commit: %v", err)
|
|
}
|
|
|
|
log.Printf("migrated %d rows to %s", n, *out)
|
|
}
|