Compare commits

..

5 Commits

Author SHA1 Message Date
Elisiário Couto
8228974c0c chore(ci): Bump version to 2025.9.17 2025-09-18 23:45:10 +01:00
Elisiário Couto
848eccb35b chore: Format files. 2025-09-18 23:43:08 +01:00
Elisiário Couto
25747d7d37 fix(api): Prevent duplicate notifications for existing transactions during sync.
The notification system was incorrectly sending notifications for existing
transactions that were being updated during sync operations. This change
modifies the transaction persistence logic to only return genuinely new
transactions, preventing duplicate notifications while maintaining data
integrity through INSERT OR REPLACE.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-18 23:42:11 +01:00
Elisiário Couto
b7d6cf8128 chore(ci): Bump version to 2025.9.16 2025-09-18 23:29:53 +01:00
copilot-swe-agent[bot]
6589c2dd66 fix(frontend): Add iOS safe area support for PWA sticky header
Co-authored-by: elisiariocouto <818914+elisiariocouto@users.noreply.github.com>
2025-09-18 23:28:49 +01:00
14 changed files with 106 additions and 15 deletions

View File

@@ -143,19 +143,19 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install git-cliff
run: |
wget -qO- https://github.com/orhun/git-cliff/releases/latest/download/git-cliff-2.10.0-x86_64-unknown-linux-gnu.tar.gz | tar xz
sudo mv git-cliff-*/git-cliff /usr/local/bin/
- name: Generate release notes
id: release_notes
run: |
echo "notes<<EOF" >> $GITHUB_OUTPUT
git-cliff --current >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create Release
uses: softprops/action-gh-release@v2
with:

View File

@@ -1,4 +1,46 @@
## 2025.9.17 (2025/09/18)
### Bug Fixes
- **api:** Prevent duplicate notifications for existing transactions during sync. ([25747d7d](https://github.com/elisiariocouto/leggen/commit/25747d7d372e291090764a6814f9d8d0b76aea3b))
### Miscellaneous Tasks
- Format files. ([848eccb3](https://github.com/elisiariocouto/leggen/commit/848eccb35b910c8121d15611547dca8da0b12756))
## 2025.9.17 (2025/09/18)
### Bug Fixes
- **api:** Prevent duplicate notifications for existing transactions during sync. ([25747d7d](https://github.com/elisiariocouto/leggen/commit/25747d7d372e291090764a6814f9d8d0b76aea3b))
### Miscellaneous Tasks
- Format files. ([848eccb3](https://github.com/elisiariocouto/leggen/commit/848eccb35b910c8121d15611547dca8da0b12756))
## 2025.9.16 (2025/09/18)
### Bug Fixes
- **frontend:** Add iOS safe area support for PWA sticky header ([6589c2dd](https://github.com/elisiariocouto/leggen/commit/6589c2dd666f8605cf6d1bf9ad7277734d4cd302))
## 2025.9.16 (2025/09/18)
### Bug Fixes
- **frontend:** Add iOS safe area support for PWA sticky header ([6589c2dd](https://github.com/elisiariocouto/leggen/commit/6589c2dd666f8605cf6d1bf9ad7277734d4cd302))
## 2025.9.15 (2025/09/18)
### Features

View File

@@ -6,4 +6,4 @@
<TileColor>#3B82F6</TileColor>
</tile>
</msapplication>
</browserconfig>
</browserconfig>

View File

@@ -1,4 +1,4 @@
User-agent: *
Allow: /
Sitemap: /sitemap.xml
Sitemap: /sitemap.xml

View File

@@ -1,4 +1,4 @@
{
"preset": "minimal-2023",
"images": ["public/favicon.svg"]
}
}

View File

@@ -33,7 +33,7 @@ export default function Header({ setSidebarOpen }: HeaderProps) {
});
return (
<header className="lg:static sticky top-0 z-50 bg-card shadow-sm border-b border-border">
<header className="lg:static sticky top-0 z-50 bg-card shadow-sm border-b border-border pt-safe-top">
<div className="flex items-center justify-between h-16 px-6">
<div className="flex items-center">
<button

View File

@@ -33,11 +33,11 @@ export function PWAInstallPrompt({ onInstall }: PWAPromptProps) {
try {
await deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
if (outcome === "accepted") {
onInstall?.();
}
setDeferredPrompt(null);
setShowPrompt(false);
} catch (error) {
@@ -153,4 +153,4 @@ export function PWAUpdatePrompt({ updateAvailable, onUpdate }: PWAUpdatePromptPr
</div>
</div>
);
}
}

View File

@@ -34,4 +34,4 @@ export function usePWA(): PWAUpdate {
updateAvailable,
updateSW,
};
}
}

View File

@@ -29,6 +29,12 @@
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
/* iOS Safe Area Support for PWA */
--safe-area-inset-top: env(safe-area-inset-top, 0px);
--safe-area-inset-right: env(safe-area-inset-right, 0px);
--safe-area-inset-bottom: env(safe-area-inset-bottom, 0px);
--safe-area-inset-left: env(safe-area-inset-left, 0px);
}
.dark {
--background: 222.2 84% 4.9%;

View File

@@ -9,6 +9,12 @@ export default {
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
spacing: {
'safe-top': 'var(--safe-area-inset-top)',
'safe-bottom': 'var(--safe-area-inset-bottom)',
'safe-left': 'var(--safe-area-inset-left)',
'safe-right': 'var(--safe-area-inset-right)',
},
colors: {
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",

View File

@@ -693,7 +693,7 @@ class DatabaseService:
# Add the display_name column
cursor.execute("""
ALTER TABLE accounts
ALTER TABLE accounts
ADD COLUMN display_name TEXT
""")
@@ -857,6 +857,14 @@ class DatabaseService:
for transaction in transactions:
try:
# Check if transaction already exists before insertion
cursor.execute(
"""SELECT COUNT(*) FROM transactions
WHERE accountId = ? AND transactionId = ?""",
(transaction["accountId"], transaction["transactionId"]),
)
exists = cursor.fetchone()[0] > 0
cursor.execute(
insert_sql,
(
@@ -873,7 +881,11 @@ class DatabaseService:
json.dumps(transaction["rawTransaction"]),
),
)
new_transactions.append(transaction)
# Only add to new_transactions if it didn't exist before
if not exists:
new_transactions.append(transaction)
except sqlite3.IntegrityError as e:
logger.warning(
f"Failed to insert transaction {transaction.get('transactionId')}: {e}"

View File

@@ -1,6 +1,6 @@
[project]
name = "leggen"
version = "2025.9.15"
version = "2025.9.17"
description = "An Open Banking CLI"
authors = [{ name = "Elisiário Couto", email = "elisiario@couto.io" }]
requires-python = "~=3.13.0"

View File

@@ -324,6 +324,8 @@ class TestDatabaseService:
with patch("sqlite3.connect") as mock_connect:
mock_conn = mock_connect.return_value
mock_cursor = mock_conn.cursor.return_value
# Mock fetchone to return (0,) indicating transaction doesn't exist yet
mock_cursor.fetchone.return_value = (0,)
result = await database_service._persist_transactions_sqlite(
"test-account-123", sample_transactions_db_format
@@ -338,6 +340,29 @@ class TestDatabaseService:
mock_conn.commit.assert_called_once()
mock_conn.close.assert_called_once()
async def test_persist_transactions_sqlite_duplicate_detection(
self, database_service, sample_transactions_db_format
):
"""Test that existing transactions are not returned as new."""
with patch("sqlite3.connect") as mock_connect:
mock_conn = mock_connect.return_value
mock_cursor = mock_conn.cursor.return_value
# Mock fetchone to return (1,) indicating transaction already exists
mock_cursor.fetchone.return_value = (1,)
result = await database_service._persist_transactions_sqlite(
"test-account-123", sample_transactions_db_format
)
# Should return empty list since all transactions already exist
assert len(result) == 0
# Verify database operations still happened (INSERT OR REPLACE executed)
mock_connect.assert_called()
mock_cursor.execute.assert_called()
mock_conn.commit.assert_called_once()
mock_conn.close.assert_called_once()
async def test_persist_transactions_sqlite_error(self, database_service):
"""Test handling error during transaction persistence."""
with patch("sqlite3.connect") as mock_connect:

2
uv.lock generated
View File

@@ -220,7 +220,7 @@ wheels = [
[[package]]
name = "leggen"
version = "2025.9.15"
version = "2025.9.17"
source = { editable = "." }
dependencies = [
{ name = "apscheduler" },