corpus

Log | Files | Refs | README | LICENSE

commit c30098bccb7d0e7ba0a8b67a7577e9ca1d863058
parent 14fa6fef846e74b2eb12a3a5ba2a3e98c2f85f6a
Author: mtmn <miro@haravara.org>
Date:   Thu, 16 Apr 2026 23:27:19 +0200

feat: duckdb chepoint to a bucket

Diffstat:
Msrc/Db.purs | 26++++++++++++++------------
Mtest/Main.purs | 35++++++++---------------------------
2 files changed, 22 insertions(+), 39 deletions(-)

diff --git a/src/Db.purs b/src/Db.purs @@ -18,12 +18,12 @@ import Types (Listen(..), TrackMetadata(..), MbidMapping(..), Stats(..), StatsEn import Data.Traversable (traverse) import Foreign.Object as Object import Data.Nullable (Nullable, toMaybe, toNullable) -import Data.Array (mapMaybe, uncons, (!!)) +import Data.Array (mapMaybe, uncons, (!!), last) import Data.Int (fromString) -import Data.String (lastIndexOf, Pattern(..), take) +import Data.String (Pattern(..), split, stripSuffix) import Control.Monad.Rec.Class (forever) import Node.FS.Aff as FSA -import Node.FS.Perms (mkPerms, all, read) as Perms +import S3 as S3 import Log as Log foreign import data Connection :: Type @@ -59,10 +59,13 @@ checkpoint conn = makeAff \cb -> do Nothing -> cb (Right unit) pure nonCanceler -dirName :: String -> String -dirName path = case lastIndexOf (Pattern "/") path of - Just i -> take (i + 1) path - Nothing -> "./" +dbBaseName :: String -> String +dbBaseName path = + let + parts = split (Pattern "/") path + name = fromMaybe path (last parts) + in + fromMaybe name (stripSuffix (Pattern ".db") name) performBackup :: Connection -> String -> Aff Unit performBackup conn dbFile = do @@ -72,11 +75,10 @@ performBackup conn dbFile = do ts = case formatDateTime "YYYY-MM-DDTHH:mm:ss" dt of Right s -> s Left _ -> "unknown" - let dir = dirName dbFile <> "backup/" - void $ try $ FSA.mkdir' dir { recursive: true, mode: Perms.mkPerms Perms.all Perms.all Perms.read } - let dest = dir <> "corpus-" <> ts <> ".db" - FSA.copyFile dbFile dest - Log.info $ "Backup saved locally: " <> dest + let key = "backups/" <> dbBaseName dbFile <> "-" <> ts <> ".db" + buf <- FSA.readFile dbFile + S3.uploadToS3 key buf "application/octet-stream" + Log.info $ "Backup uploaded to S3: " <> key backupDb :: Connection -> String -> Number -> Aff Unit backupDb conn dbFile intervalMs = forever do diff --git a/test/Main.purs b/test/Main.purs @@ -14,13 +14,10 @@ import Test.Spec.Assertions (shouldEqual, fail) import Test.Spec.Reporter.Console (consoleReporter) import Test.Spec.Runner.Node (runSpecAndExitProcess) import Types (Listen(..), ListenBrainzResponse(..), MbidMapping(..), Payload(..), Stats(..), StatsEntry(..), TrackMetadata(..)) -import Db (connect, initDb, checkExists, upsertScrobble, getScrobbles, initReleaseMetadata, upsertReleaseMetadata, getStats, dirName, performBackup) +import Db (connect, initDb, checkExists, upsertScrobble, getScrobbles, initReleaseMetadata, upsertReleaseMetadata, getStats, dbBaseName) import Data.Argonaut.Core (Json) import Main (sanitizeKey, listenBrainzUrl, lastfmTrackToListen) import S3 (getS3Url) -import Node.FS.Aff as FSA -import Node.FS.Perms (mkPerms, all, read) as Perms -import Effect.Aff (try) main :: Effect Unit main = do @@ -162,29 +159,13 @@ main = do length listensEmpty `shouldEqual` 0 describe "Corpus Backup" do - describe "dirName" do - it "extracts directory from an absolute path" do - dirName "/app/data/corpus.db" `shouldEqual` "/app/data/" - it "extracts directory from a nested path" do - dirName "/tmp/test/corpus.db" `shouldEqual` "/tmp/test/" - it "returns ./ for a bare filename" do - dirName "corpus.db" `shouldEqual` "./" - - it "local backup creates a file in backup/ alongside the db" do - let testDir = "/tmp/corpus-backup-test" - let dbPath = testDir <> "/corpus.db" - let backupDir = testDir <> "/backup" - -- clean up any previous run - void $ try $ FSA.rm' testDir { force: true, recursive: true, maxRetries: 0, retryDelay: 100 } - FSA.mkdir' testDir { recursive: true, mode: Perms.mkPerms Perms.all Perms.all Perms.read } - conn <- connect dbPath - initDb conn - initReleaseMetadata conn - performBackup conn dbPath - files <- FSA.readdir backupDir - length files `shouldEqual` 1 - -- cleanup - void $ try $ FSA.rm' testDir { force: true, recursive: true, maxRetries: 0, retryDelay: 100 } + describe "dbBaseName" do + it "extracts base name from an absolute path" do + dbBaseName "/app/data/corpus.db" `shouldEqual` "corpus" + it "extracts base name from a nested path" do + dbBaseName "/tmp/test/mymusic.db" `shouldEqual` "mymusic" + it "returns the name without extension for a bare filename" do + dbBaseName "corpus.db" `shouldEqual` "corpus" describe "Last.fm Support" do let parseTrack :: String -> Json