Fix .gitignore excluding frontend plugins and test files
The broad plugins/ and imports/ patterns were matching directories at all levels, excluding frontend/src/plugins/vuetify.ts, frontend/src/views/imports/, frontend/src/views/plugins/, and src/Purrse.Tests/Plugins/. Changed to /plugins/ and /imports/ to only match root-level directories. This was the root cause of the Docker build failure - vuetify.ts was missing from the repo. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+3
-3
@@ -35,6 +35,6 @@ docker-compose.override.yml
|
|||||||
# Claude Code
|
# Claude Code
|
||||||
.claude/
|
.claude/
|
||||||
|
|
||||||
# Plugins and imports (runtime data)
|
# Plugins and imports (runtime data, root-level only)
|
||||||
plugins/
|
/plugins/
|
||||||
imports/
|
/imports/
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import 'vuetify/styles'
|
||||||
|
import { createVuetify } from 'vuetify'
|
||||||
|
import * as components from 'vuetify/components'
|
||||||
|
import * as directives from 'vuetify/directives'
|
||||||
|
|
||||||
|
export default createVuetify({
|
||||||
|
components,
|
||||||
|
directives,
|
||||||
|
theme: {
|
||||||
|
defaultTheme: 'dark',
|
||||||
|
themes: {
|
||||||
|
dark: {
|
||||||
|
dark: true,
|
||||||
|
colors: {
|
||||||
|
primary: '#7C4DFF',
|
||||||
|
secondary: '#FF6D00',
|
||||||
|
accent: '#00BFA5',
|
||||||
|
error: '#FF5252',
|
||||||
|
warning: '#FFC107',
|
||||||
|
info: '#2196F3',
|
||||||
|
success: '#4CAF50',
|
||||||
|
background: '#121212',
|
||||||
|
surface: '#1E1E1E',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
light: {
|
||||||
|
dark: false,
|
||||||
|
colors: {
|
||||||
|
primary: '#6200EA',
|
||||||
|
secondary: '#FF6D00',
|
||||||
|
accent: '#00BFA5',
|
||||||
|
error: '#FF5252',
|
||||||
|
warning: '#FFC107',
|
||||||
|
info: '#2196F3',
|
||||||
|
success: '#4CAF50',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1 class="text-h4 mb-4">Import Transactions</h1>
|
||||||
|
<v-card>
|
||||||
|
<v-card-text>
|
||||||
|
<v-file-input v-model="file" label="Select file to import" accept=".ofx,.qfx,.csv,.qif" prepend-icon="mdi-upload" />
|
||||||
|
<v-select v-model="selectedAccount" label="Target Account" :items="accounts" item-title="name" item-value="id" />
|
||||||
|
<v-btn color="primary" :disabled="!file || !selectedAccount" @click="uploadFile" :loading="uploading">Import</v-btn>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useAccountsStore } from '@/stores/accounts'
|
||||||
|
import api from '@/services/api'
|
||||||
|
|
||||||
|
const accountsStore = useAccountsStore()
|
||||||
|
const accounts = ref(accountsStore.accounts)
|
||||||
|
const file = ref<File | null>(null)
|
||||||
|
const selectedAccount = ref('')
|
||||||
|
const uploading = ref(false)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await accountsStore.fetchAccounts()
|
||||||
|
accounts.value = accountsStore.accounts
|
||||||
|
})
|
||||||
|
|
||||||
|
async function uploadFile() {
|
||||||
|
if (!file.value || !selectedAccount.value) return
|
||||||
|
uploading.value = true
|
||||||
|
try {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file.value)
|
||||||
|
await api.post(`/imports/upload?accountId=${selectedAccount.value}`, formData, { headers: { 'Content-Type': 'multipart/form-data' } })
|
||||||
|
file.value = null
|
||||||
|
} finally {
|
||||||
|
uploading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1 class="text-h4 mb-4">Plugins</h1>
|
||||||
|
<v-card><v-card-text><p class="text-medium-emphasis">Plugin marketplace coming in Phase 5. Extend Purrse with community-built integrations and features.</p></v-card-text></v-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Purrse.Plugins.CSV;
|
||||||
|
|
||||||
|
namespace Purrse.Tests.Plugins;
|
||||||
|
|
||||||
|
public class CSVParserTests
|
||||||
|
{
|
||||||
|
private readonly CSVParserPlugin _parser = new();
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanParse_CSVFile_ReturnsTrue()
|
||||||
|
{
|
||||||
|
_parser.CanParse("transactions.csv", Stream.Null).Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanParse_OFXFile_ReturnsFalse()
|
||||||
|
{
|
||||||
|
_parser.CanParse("statement.ofx", Stream.Null).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ParseAsync_StandardCSV_ParsesTransactions()
|
||||||
|
{
|
||||||
|
var csv = "Date,Description,Amount\n01/15/2024,WALMART,-42.50\n01/20/2024,PAYCHECK,1500.00\n";
|
||||||
|
|
||||||
|
using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(csv));
|
||||||
|
var result = await _parser.ParseAsync(stream, "test.csv");
|
||||||
|
|
||||||
|
result.Success.Should().BeTrue();
|
||||||
|
result.Transactions.Should().HaveCount(2);
|
||||||
|
result.Transactions[0].PayeeName.Should().Be("WALMART");
|
||||||
|
result.Transactions[0].Amount.Should().Be(-42.50m);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ParseAsync_DebitCreditColumns_ParsesCorrectly()
|
||||||
|
{
|
||||||
|
var csv = "Date,Description,Debit,Credit\n01/15/2024,WALMART,42.50,\n01/20/2024,PAYCHECK,,1500.00\n";
|
||||||
|
|
||||||
|
using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(csv));
|
||||||
|
var result = await _parser.ParseAsync(stream, "test.csv");
|
||||||
|
|
||||||
|
result.Success.Should().BeTrue();
|
||||||
|
result.Transactions.Should().HaveCount(2);
|
||||||
|
result.Transactions[0].Amount.Should().Be(-42.50m);
|
||||||
|
result.Transactions[1].Amount.Should().Be(1500.00m);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Purrse.Plugins.OFX;
|
||||||
|
|
||||||
|
namespace Purrse.Tests.Plugins;
|
||||||
|
|
||||||
|
public class OFXParserTests
|
||||||
|
{
|
||||||
|
private readonly OFXParserPlugin _parser = new();
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanParse_OFXFile_ReturnsTrue()
|
||||||
|
{
|
||||||
|
_parser.CanParse("statement.ofx", Stream.Null).Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanParse_QFXFile_ReturnsTrue()
|
||||||
|
{
|
||||||
|
_parser.CanParse("download.qfx", Stream.Null).Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanParse_CSVFile_ReturnsFalse()
|
||||||
|
{
|
||||||
|
_parser.CanParse("data.csv", Stream.Null).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ParseAsync_ValidXmlOFX_ParsesTransactions()
|
||||||
|
{
|
||||||
|
var ofxContent = @"<?xml version=""1.0"" encoding=""UTF-8""?>
|
||||||
|
<OFX>
|
||||||
|
<BANKMSGSRSV1>
|
||||||
|
<STMTTRNRS>
|
||||||
|
<STMTRS>
|
||||||
|
<BANKACCTFROM>
|
||||||
|
<ACCTID>123456789</ACCTID>
|
||||||
|
</BANKACCTFROM>
|
||||||
|
<BANKTRANLIST>
|
||||||
|
<DTSTART>20240101</DTSTART>
|
||||||
|
<DTEND>20240131</DTEND>
|
||||||
|
<STMTTRN>
|
||||||
|
<TRNTYPE>DEBIT</TRNTYPE>
|
||||||
|
<DTPOSTED>20240115</DTPOSTED>
|
||||||
|
<TRNAMT>-42.50</TRNAMT>
|
||||||
|
<FITID>20240115001</FITID>
|
||||||
|
<NAME>WALMART STORE #1234</NAME>
|
||||||
|
<MEMO>Purchase</MEMO>
|
||||||
|
</STMTTRN>
|
||||||
|
<STMTTRN>
|
||||||
|
<TRNTYPE>CREDIT</TRNTYPE>
|
||||||
|
<DTPOSTED>20240120</DTPOSTED>
|
||||||
|
<TRNAMT>1500.00</TRNAMT>
|
||||||
|
<FITID>20240120001</FITID>
|
||||||
|
<NAME>DIRECT DEPOSIT</NAME>
|
||||||
|
</STMTTRN>
|
||||||
|
</BANKTRANLIST>
|
||||||
|
</STMTRS>
|
||||||
|
</STMTTRNRS>
|
||||||
|
</BANKMSGSRSV1>
|
||||||
|
</OFX>";
|
||||||
|
|
||||||
|
using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(ofxContent));
|
||||||
|
var result = await _parser.ParseAsync(stream, "test.ofx");
|
||||||
|
|
||||||
|
result.Success.Should().BeTrue();
|
||||||
|
result.Transactions.Should().HaveCount(2);
|
||||||
|
result.AccountIdentifier.Should().Be("123456789");
|
||||||
|
|
||||||
|
var debit = result.Transactions[0];
|
||||||
|
debit.Amount.Should().Be(-42.50m);
|
||||||
|
debit.PayeeName.Should().Be("WALMART STORE #1234");
|
||||||
|
debit.FitId.Should().Be("20240115001");
|
||||||
|
debit.Date.Should().Be(new DateTime(2024, 1, 15));
|
||||||
|
|
||||||
|
var credit = result.Transactions[1];
|
||||||
|
credit.Amount.Should().Be(1500.00m);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ParseAsync_EmptyStream_ReturnsFailure()
|
||||||
|
{
|
||||||
|
using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(""));
|
||||||
|
var result = await _parser.ParseAsync(stream, "empty.ofx");
|
||||||
|
|
||||||
|
result.Success.Should().BeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Purrse.Plugins.QIF;
|
||||||
|
|
||||||
|
namespace Purrse.Tests.Plugins;
|
||||||
|
|
||||||
|
public class QIFParserTests
|
||||||
|
{
|
||||||
|
private readonly QIFParserPlugin _parser = new();
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanParse_QIFFile_ReturnsTrue()
|
||||||
|
{
|
||||||
|
_parser.CanParse("data.qif", Stream.Null).Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ParseAsync_ValidQIF_ParsesTransactions()
|
||||||
|
{
|
||||||
|
var qif = "!Type:Bank\nD01/15/2024\nT-42.50\nPWALMART\nMGroceries\n^\nD01/20/2024\nT1500.00\nPDIRECT DEPOSIT\n^\n";
|
||||||
|
|
||||||
|
using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(qif));
|
||||||
|
var result = await _parser.ParseAsync(stream, "test.qif");
|
||||||
|
|
||||||
|
result.Success.Should().BeTrue();
|
||||||
|
result.Transactions.Should().HaveCount(2);
|
||||||
|
result.Transactions[0].Amount.Should().Be(-42.50m);
|
||||||
|
result.Transactions[0].PayeeName.Should().Be("WALMART");
|
||||||
|
result.Transactions[1].Amount.Should().Be(1500.00m);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user