whisper-money/app/Http/Controllers/Api/TransactionController.php

118 lines
3.9 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\BulkUpdateTransactionRequest;
use App\Http\Requests\Api\CheckDuplicateTransactionsRequest;
use App\Models\Transaction;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class TransactionController extends Controller
{
/**
* Return paginated transactions for the authenticated user with optional filters.
*/
public function index(Request $request): JsonResponse
{
$query = $request->user()
->transactions()
->with('labels');
if ($request->query('encrypted') === 'true') {
$query->where(fn ($q) => $q->whereNotNull('description_iv')->orWhereNotNull('notes_iv'));
}
if ($accountId = $request->query('account_id')) {
$query->where('account_id', $accountId)
->orderBy('transaction_date', 'desc')
->orderBy('id', 'desc');
}
$perPage = min(max((int) $request->query('per_page', 100), 1), 100);
$transactions = $query->simplePaginate($perPage);
return response()->json($transactions);
}
/**
* Flag which of the given (date, amount, description) tuples already exist
* on the account. Replaces the old client-side IndexedDB duplicate check.
*
* Matching mirrors the previous frontend logic: same day, exact amount (both
* in integer cents), and case-insensitive description with collapsed
* whitespace. Returns a boolean per input transaction, in order.
*/
public function checkDuplicates(CheckDuplicateTransactionsRequest $request): JsonResponse
{
$validated = $request->validated();
$account = $request->user()->accounts()->findOrFail($validated['account_id']);
$incoming = $validated['transactions'];
$dates = array_map(fn (array $t): string => substr($t['transaction_date'], 0, 10), $incoming);
$existing = $account->transactions()
->whereBetween('transaction_date', [min($dates), max($dates)])
->get(['transaction_date', 'amount', 'description']);
$seen = [];
foreach ($existing as $transaction) {
$seen[$this->duplicateKey(
$transaction->transaction_date->format('Y-m-d'),
(int) $transaction->amount,
$transaction->description,
)] = true;
}
$duplicates = array_map(
fn (array $t): bool => isset($seen[$this->duplicateKey(
substr($t['transaction_date'], 0, 10),
(int) $t['amount'],
$t['description'],
)]),
$incoming,
);
return response()->json(['duplicates' => $duplicates]);
}
private function duplicateKey(string $date, int $amount, string $description): string
{
$normalized = preg_replace('/\s+/', ' ', trim(mb_strtolower($description)));
return $date.'|'.$amount.'|'.$normalized;
}
/**
* Bulk update transactions (used for decryption migration).
*/
public function bulkUpdate(BulkUpdateTransactionRequest $request): JsonResponse
{
$validated = $request->validated();
$transactionIds = collect($validated['transactions'])->pluck('id');
$userTransactionIds = $request->user()
->transactions()
->whereIn('id', $transactionIds)
->pluck('id');
if ($userTransactionIds->count() !== $transactionIds->count()) {
abort(403, 'Some transactions do not belong to the authenticated user.');
}
foreach ($validated['transactions'] as $data) {
$updateData = collect($data)->except('id')->toArray();
Transaction::query()
->where('id', $data['id'])
->toBase()
->update($updateData);
}
return response()->json(['message' => 'Transactions updated successfully.']);
}
}