diff --git a/app/Http/Requests/Settings/StoreAutomationRuleRequest.php b/app/Http/Requests/Settings/StoreAutomationRuleRequest.php index 5c130b4b..e05b5c62 100644 --- a/app/Http/Requests/Settings/StoreAutomationRuleRequest.php +++ b/app/Http/Requests/Settings/StoreAutomationRuleRequest.php @@ -34,7 +34,7 @@ class StoreAutomationRuleRequest extends FormRequest }], 'action_category_id' => [ 'nullable', - 'integer', + 'string', Rule::exists('categories', 'id')->where(function ($query) { $query->where('user_id', auth()->id()); }), diff --git a/app/Http/Requests/Settings/UpdateAutomationRuleRequest.php b/app/Http/Requests/Settings/UpdateAutomationRuleRequest.php index 0817e003..ed27d67e 100644 --- a/app/Http/Requests/Settings/UpdateAutomationRuleRequest.php +++ b/app/Http/Requests/Settings/UpdateAutomationRuleRequest.php @@ -34,7 +34,7 @@ class UpdateAutomationRuleRequest extends FormRequest }], 'action_category_id' => [ 'nullable', - 'integer', + 'string', Rule::exists('categories', 'id')->where(function ($query) { $query->where('user_id', auth()->id()); }), diff --git a/app/Models/Account.php b/app/Models/Account.php index b57d767f..2aea1829 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Enums\AccountType; +use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -12,7 +13,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; class Account extends Model { /** @use HasFactory<\Database\Factories\AccountFactory> */ - use HasFactory, SoftDeletes; + use HasFactory, HasUuids, SoftDeletes; protected $fillable = [ 'user_id', diff --git a/app/Models/AccountBalance.php b/app/Models/AccountBalance.php index 11bdb597..602947fe 100644 --- a/app/Models/AccountBalance.php +++ b/app/Models/AccountBalance.php @@ -29,9 +29,4 @@ class AccountBalance extends Model { return $this->belongsTo(Account::class); } - - public function newUniqueId(): string - { - return (string) \Illuminate\Support\Str::uuid7(); - } } diff --git a/app/Models/AutomationRule.php b/app/Models/AutomationRule.php index 56214956..5720e6bc 100644 --- a/app/Models/AutomationRule.php +++ b/app/Models/AutomationRule.php @@ -2,6 +2,7 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -10,7 +11,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; class AutomationRule extends Model { /** @use HasFactory<\Database\Factories\AutomationRuleFactory> */ - use HasFactory, SoftDeletes; + use HasFactory, HasUuids, SoftDeletes; protected $fillable = [ 'user_id', diff --git a/app/Models/Bank.php b/app/Models/Bank.php index ce9cdfcb..a3afd859 100644 --- a/app/Models/Bank.php +++ b/app/Models/Bank.php @@ -2,6 +2,7 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -11,7 +12,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; class Bank extends Model { /** @use HasFactory<\Database\Factories\BankFactory> */ - use HasFactory, SoftDeletes; + use HasFactory, HasUuids, SoftDeletes; protected $fillable = [ 'name', diff --git a/app/Models/Category.php b/app/Models/Category.php index c8149b5b..33ba5b9b 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -2,6 +2,7 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -11,7 +12,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; class Category extends Model { /** @use HasFactory<\Database\Factories\CategoryFactory> */ - use HasFactory, SoftDeletes; + use HasFactory, HasUuids, SoftDeletes; protected $fillable = [ 'name', diff --git a/app/Models/EncryptedMessage.php b/app/Models/EncryptedMessage.php index c1f2fbc7..8794295c 100644 --- a/app/Models/EncryptedMessage.php +++ b/app/Models/EncryptedMessage.php @@ -2,11 +2,14 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; class EncryptedMessage extends Model { + use HasUuids; + protected $fillable = [ 'user_id', 'encrypted_content', diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index 370b5e46..7cd4ac5a 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -48,9 +48,4 @@ class Transaction extends Model { return $this->belongsTo(Category::class); } - - public function newUniqueId(): string - { - return (string) \Illuminate\Support\Str::uuid7(); - } } diff --git a/app/Models/User.php b/app/Models/User.php index 1baa18a9..4a2a6c28 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -3,6 +3,7 @@ namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; +use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; @@ -13,7 +14,7 @@ use Laravel\Fortify\TwoFactorAuthenticatable; class User extends Authenticatable { /** @use HasFactory<\Database\Factories\UserFactory> */ - use HasFactory, Notifiable, TwoFactorAuthenticatable; + use HasFactory, HasUuids, Notifiable, TwoFactorAuthenticatable; /** * The attributes that are mass assignable. diff --git a/database/migrations/2025_11_15_195739_convert_all_ids_to_uuid.php b/database/migrations/2025_11_15_195739_convert_all_ids_to_uuid.php new file mode 100644 index 00000000..b123f724 --- /dev/null +++ b/database/migrations/2025_11_15_195739_convert_all_ids_to_uuid.php @@ -0,0 +1,329 @@ +dropColumn('user_id'); + }); + + Schema::table('encrypted_messages', function (Blueprint $table) { + $table->dropForeign(['user_id']); + }); + + Schema::table('banks', function (Blueprint $table) { + $table->dropForeign(['user_id']); + }); + + Schema::table('accounts', function (Blueprint $table) { + $table->dropForeign(['user_id']); + $table->dropForeign(['bank_id']); + }); + + Schema::table('categories', function (Blueprint $table) { + $table->dropForeign(['user_id']); + }); + + Schema::table('transactions', function (Blueprint $table) { + $table->dropForeign(['user_id']); + $table->dropForeign(['account_id']); + $table->dropForeign(['category_id']); + }); + + Schema::table('automation_rules', function (Blueprint $table) { + $table->dropForeign(['user_id']); + $table->dropForeign(['action_category_id']); + }); + + Schema::table('account_balances', function (Blueprint $table) { + $table->dropForeign(['account_id']); + }); + + DB::statement('DELETE FROM users'); + DB::statement('DELETE FROM banks'); + DB::statement('DELETE FROM accounts'); + DB::statement('DELETE FROM categories'); + DB::statement('DELETE FROM transactions'); + DB::statement('DELETE FROM automation_rules'); + DB::statement('DELETE FROM encrypted_messages'); + DB::statement('DELETE FROM account_balances'); + + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('id'); + }); + Schema::table('users', function (Blueprint $table) { + $table->uuid('id')->primary()->first(); + }); + + Schema::table('banks', function (Blueprint $table) { + $table->dropColumn('id'); + $table->dropColumn('user_id'); + }); + Schema::table('banks', function (Blueprint $table) { + $table->uuid('id')->primary()->first(); + $table->uuid('user_id')->nullable()->after('id'); + }); + + Schema::table('accounts', function (Blueprint $table) { + $table->dropColumn('id'); + $table->dropColumn('user_id'); + $table->dropColumn('bank_id'); + }); + Schema::table('accounts', function (Blueprint $table) { + $table->uuid('id')->primary()->first(); + $table->uuid('user_id')->after('id'); + $table->uuid('bank_id')->after('name_iv'); + }); + + Schema::table('categories', function (Blueprint $table) { + $table->dropColumn('id'); + $table->dropColumn('user_id'); + }); + Schema::table('categories', function (Blueprint $table) { + $table->uuid('id')->primary()->first(); + $table->uuid('user_id')->after('color'); + }); + + Schema::table('transactions', function (Blueprint $table) { + $table->dropColumn('user_id'); + $table->dropColumn('account_id'); + $table->dropColumn('category_id'); + }); + Schema::table('transactions', function (Blueprint $table) { + $table->uuid('user_id')->after('id'); + $table->uuid('account_id')->after('user_id'); + $table->uuid('category_id')->nullable()->after('account_id'); + }); + + Schema::table('automation_rules', function (Blueprint $table) { + $table->dropColumn('id'); + $table->dropColumn('user_id'); + $table->dropColumn('action_category_id'); + }); + Schema::table('automation_rules', function (Blueprint $table) { + $table->uuid('id')->primary()->first(); + $table->uuid('user_id')->after('id'); + $table->uuid('action_category_id')->nullable()->after('rules_json'); + }); + + Schema::table('encrypted_messages', function (Blueprint $table) { + $table->dropColumn('id'); + $table->dropColumn('user_id'); + }); + Schema::table('encrypted_messages', function (Blueprint $table) { + $table->uuid('id')->primary()->first(); + $table->uuid('user_id')->after('id'); + }); + + Schema::table('account_balances', function (Blueprint $table) { + $table->dropColumn('account_id'); + }); + Schema::table('account_balances', function (Blueprint $table) { + $table->uuid('account_id')->after('id'); + }); + + Schema::table('sessions', function (Blueprint $table) { + $table->uuid('user_id')->nullable()->after('id')->index(); + }); + + Schema::table('banks', function (Blueprint $table) { + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + }); + + Schema::table('accounts', function (Blueprint $table) { + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('bank_id')->references('id')->on('banks')->onDelete('cascade'); + }); + + Schema::table('categories', function (Blueprint $table) { + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + }); + + Schema::table('transactions', function (Blueprint $table) { + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); + $table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade'); + }); + + Schema::table('automation_rules', function (Blueprint $table) { + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('action_category_id')->references('id')->on('categories')->onDelete('set null'); + }); + + Schema::table('encrypted_messages', function (Blueprint $table) { + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + }); + + Schema::table('account_balances', function (Blueprint $table) { + $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); + }); + + DB::statement('SET FOREIGN_KEY_CHECKS=1'); + } + + public function down(): void + { + DB::statement('SET FOREIGN_KEY_CHECKS=0'); + + Schema::table('sessions', function (Blueprint $table) { + $table->dropColumn('user_id'); + }); + + Schema::table('encrypted_messages', function (Blueprint $table) { + $table->dropForeign(['user_id']); + }); + + Schema::table('accounts', function (Blueprint $table) { + $table->dropForeign(['user_id']); + $table->dropForeign(['bank_id']); + }); + + Schema::table('categories', function (Blueprint $table) { + $table->dropForeign(['user_id']); + }); + + Schema::table('transactions', function (Blueprint $table) { + $table->dropForeign(['user_id']); + $table->dropForeign(['account_id']); + $table->dropForeign(['category_id']); + }); + + Schema::table('automation_rules', function (Blueprint $table) { + $table->dropForeign(['user_id']); + $table->dropForeign(['action_category_id']); + }); + + Schema::table('account_balances', function (Blueprint $table) { + $table->dropForeign(['account_id']); + }); + + DB::statement('DELETE FROM users'); + DB::statement('DELETE FROM banks'); + DB::statement('DELETE FROM accounts'); + DB::statement('DELETE FROM categories'); + DB::statement('DELETE FROM transactions'); + DB::statement('DELETE FROM automation_rules'); + DB::statement('DELETE FROM encrypted_messages'); + DB::statement('DELETE FROM account_balances'); + + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('id'); + }); + Schema::table('users', function (Blueprint $table) { + $table->id()->first(); + }); + + Schema::table('banks', function (Blueprint $table) { + $table->dropColumn('id'); + $table->dropColumn('user_id'); + }); + Schema::table('banks', function (Blueprint $table) { + $table->id()->first(); + $table->foreignId('user_id')->nullable()->after('id'); + }); + + Schema::table('accounts', function (Blueprint $table) { + $table->dropColumn('id'); + $table->dropColumn('user_id'); + $table->dropColumn('bank_id'); + }); + Schema::table('accounts', function (Blueprint $table) { + $table->id()->first(); + $table->foreignId('user_id')->after('id'); + $table->foreignId('bank_id')->after('name_iv'); + }); + + Schema::table('categories', function (Blueprint $table) { + $table->dropColumn('id'); + $table->dropColumn('user_id'); + }); + Schema::table('categories', function (Blueprint $table) { + $table->id()->first(); + $table->foreignId('user_id')->after('color'); + }); + + Schema::table('transactions', function (Blueprint $table) { + $table->dropColumn('user_id'); + $table->dropColumn('account_id'); + $table->dropColumn('category_id'); + }); + Schema::table('transactions', function (Blueprint $table) { + $table->foreignId('user_id')->after('id'); + $table->foreignId('account_id')->after('user_id'); + $table->foreignId('category_id')->nullable()->after('account_id'); + }); + + Schema::table('automation_rules', function (Blueprint $table) { + $table->dropColumn('id'); + $table->dropColumn('user_id'); + $table->dropColumn('action_category_id'); + }); + Schema::table('automation_rules', function (Blueprint $table) { + $table->id()->first(); + $table->foreignId('user_id')->after('id'); + $table->foreignId('action_category_id')->nullable()->after('rules_json'); + }); + + Schema::table('encrypted_messages', function (Blueprint $table) { + $table->dropColumn('id'); + $table->dropColumn('user_id'); + }); + Schema::table('encrypted_messages', function (Blueprint $table) { + $table->id()->first(); + $table->foreignId('user_id')->after('id'); + }); + + Schema::table('account_balances', function (Blueprint $table) { + $table->dropColumn('account_id'); + }); + Schema::table('account_balances', function (Blueprint $table) { + $table->foreignId('account_id')->after('id'); + }); + + Schema::table('sessions', function (Blueprint $table) { + $table->foreignId('user_id')->nullable()->after('id')->index(); + }); + + Schema::table('banks', function (Blueprint $table) { + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + }); + + Schema::table('accounts', function (Blueprint $table) { + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('bank_id')->references('id')->on('banks')->onDelete('cascade'); + }); + + Schema::table('categories', function (Blueprint $table) { + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + }); + + Schema::table('transactions', function (Blueprint $table) { + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); + $table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade'); + }); + + Schema::table('automation_rules', function (Blueprint $table) { + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('action_category_id')->references('id')->on('categories')->onDelete('set null'); + }); + + Schema::table('encrypted_messages', function (Blueprint $table) { + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + }); + + Schema::table('account_balances', function (Blueprint $table) { + $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); + }); + + DB::statement('SET FOREIGN_KEY_CHECKS=1'); + } +}; diff --git a/resources/js/components/automation-rules/create-automation-rule-dialog.tsx b/resources/js/components/automation-rules/create-automation-rule-dialog.tsx index 49358347..dfb643a8 100644 --- a/resources/js/components/automation-rules/create-automation-rule-dialog.tsx +++ b/resources/js/components/automation-rules/create-automation-rule-dialog.tsx @@ -107,9 +107,7 @@ export function CreateAutomationRuleDialog({ title: title.trim(), priority: parseInt(priority, 10), rules_json: JSON.stringify(jsonLogic), - action_category_id: categoryId - ? parseInt(categoryId, 10) - : null, + action_category_id: categoryId || null, action_note: encryptedNote, action_note_iv: noteIv, }, diff --git a/resources/js/components/automation-rules/edit-automation-rule-dialog.tsx b/resources/js/components/automation-rules/edit-automation-rule-dialog.tsx index 8682e5cd..3e82505d 100644 --- a/resources/js/components/automation-rules/edit-automation-rule-dialog.tsx +++ b/resources/js/components/automation-rules/edit-automation-rule-dialog.tsx @@ -145,9 +145,7 @@ export function EditAutomationRuleDialog({ title: title.trim(), priority: parseInt(priority, 10), rules_json: JSON.stringify(jsonLogic), - action_category_id: categoryId - ? parseInt(categoryId, 10) - : null, + action_category_id: categoryId || null, action_note: encryptedNote, action_note_iv: noteIv, }, diff --git a/resources/js/components/shared/category-combobox.tsx b/resources/js/components/shared/category-combobox.tsx index 7352e228..d4009588 100644 --- a/resources/js/components/shared/category-combobox.tsx +++ b/resources/js/components/shared/category-combobox.tsx @@ -45,7 +45,7 @@ export function CategoryCombobox({ const selectedCategory = value && value !== 'null' - ? categories.find((c) => c.id === parseInt(value)) + ? categories.find((c) => c.id === value) : null; const sortedCategories = [...categories].sort((a, b) => diff --git a/resources/js/components/transactions/bulk-category-select.tsx b/resources/js/components/transactions/bulk-category-select.tsx index 45021459..50fb6ba3 100644 --- a/resources/js/components/transactions/bulk-category-select.tsx +++ b/resources/js/components/transactions/bulk-category-select.tsx @@ -17,7 +17,7 @@ export function BulkCategorySelect({ function handleChange(newValue: string) { setValue(newValue); - const categoryId = newValue === 'null' ? null : parseInt(newValue); + const categoryId = newValue === 'null' ? null : newValue; onCategoryChange(categoryId); } diff --git a/resources/js/components/transactions/category-cell.tsx b/resources/js/components/transactions/category-cell.tsx index c0e15a12..07ee91a3 100644 --- a/resources/js/components/transactions/category-cell.tsx +++ b/resources/js/components/transactions/category-cell.tsx @@ -35,12 +35,12 @@ export function CategoryCell({ return; } - const categoryId = value === 'null' ? null : parseInt(value); + const categoryId = value === 'null' ? null : value; setIsUpdating(true); try { const updateData: { - category_id: number | null; + category_id: string | null; notes?: string; notes_iv?: string; } = { diff --git a/resources/js/components/transactions/edit-transaction-dialog.tsx b/resources/js/components/transactions/edit-transaction-dialog.tsx index 7902851a..cf029b3b 100644 --- a/resources/js/components/transactions/edit-transaction-dialog.tsx +++ b/resources/js/components/transactions/edit-transaction-dialog.tsx @@ -60,7 +60,7 @@ export function EditTransactionDialog({ const [notes, setNotes] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const [decryptedAccountNames, setDecryptedAccountNames] = useState< - Map + Map >(new Map()); useEffect(() => { @@ -68,19 +68,15 @@ export function EditTransactionDialog({ setTransactionDate(transaction.transaction_date); setDescription(transaction.decryptedDescription); setAmount(transaction.amount); - setAccountId(String(transaction.account_id)); - setCategoryId( - transaction.category_id - ? String(transaction.category_id) - : 'null', - ); + setAccountId(transaction.account_id); + setCategoryId(transaction.category_id || 'null'); setNotes(transaction.decryptedNotes || ''); } else if (mode === 'create' && open) { const today = new Date().toISOString().split('T')[0]; setTransactionDate(today); setDescription(''); setAmount(0); - setAccountId(accounts.length > 0 ? String(accounts[0].id) : ''); + setAccountId(accounts.length > 0 ? accounts[0].id : ''); setCategoryId('null'); setNotes(''); } @@ -97,7 +93,7 @@ export function EditTransactionDialog({ try { const key = await importKey(keyString); - const decryptedNames = new Map(); + const decryptedNames = new Map(); await Promise.all( accounts.map(async (account) => { @@ -160,7 +156,7 @@ export function EditTransactionDialog({ setIsSubmitting(true); try { const selectedCategoryId = - categoryId === 'null' ? null : parseInt(categoryId, 10); + categoryId === 'null' ? null : categoryId; const trimmedNotes = notes.trim(); const trimmedDescription = description.trim(); @@ -186,15 +182,15 @@ export function EditTransactionDialog({ ); const selectedAccount = accounts.find( - (acc) => acc.id === parseInt(accountId, 10), + (acc) => acc.id === accountId, ); if (!selectedAccount) { throw new Error('Selected account not found'); } const createdTransaction = await transactionSyncService.create({ - user_id: 0, - account_id: parseInt(accountId, 10), + user_id: '00000000-0000-0000-0000-000000000000', + account_id: accountId, category_id: selectedCategoryId, description: encryptedDescription.encrypted, description_iv: encryptedDescription.iv, @@ -271,7 +267,7 @@ export function EditTransactionDialog({ } const selectedAccount = accounts.find( - (acc) => acc.id === parseInt(accountId, 10), + (acc) => acc.id === accountId, ); return ( diff --git a/resources/js/components/transactions/import-step-account.tsx b/resources/js/components/transactions/import-step-account.tsx index c89bed24..eefaa7e7 100644 --- a/resources/js/components/transactions/import-step-account.tsx +++ b/resources/js/components/transactions/import-step-account.tsx @@ -4,12 +4,13 @@ import { Label } from '@/components/ui/label'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; import { accountSyncService } from '@/services/account-sync'; import { type Account } from '@/types/account'; +import type { UUID } from '@/types/uuid'; import { Building2 } from 'lucide-react'; import { useEffect, useState } from 'react'; interface ImportStepAccountProps { - selectedAccountId: number | null; - onAccountSelect: (accountId: number) => void; + selectedAccountId: UUID | null; + onAccountSelect: (accountId: UUID) => void; onNext: () => void; } @@ -59,8 +60,8 @@ export function ImportStepAccount({ return (
onAccountSelect(Number(value))} + value={selectedAccountId} + onValueChange={(value) => onAccountSelect(value)} >
{accounts.map((account) => ( @@ -70,7 +71,7 @@ export function ImportStepAccount({ className="flex items-center space-x-3 rounded-lg border p-4 hover:bg-accent" > {account.bank.logo ? ( diff --git a/resources/js/lib/dexie-db.ts b/resources/js/lib/dexie-db.ts index 64d2be15..1840cc7a 100644 --- a/resources/js/lib/dexie-db.ts +++ b/resources/js/lib/dexie-db.ts @@ -28,7 +28,7 @@ const db = new Dexie('whisper_money') as Dexie & { pending_changes: EntityTable; }; -db.version(4).stores({ +db.version(5).stores({ transactions: 'id, user_id, account_id, updated_at', accounts: 'id, user_id, bank_id, updated_at', categories: 'id, user_id, updated_at', diff --git a/resources/js/lib/sync-manager.ts b/resources/js/lib/sync-manager.ts index 19728c89..51fc440d 100644 --- a/resources/js/lib/sync-manager.ts +++ b/resources/js/lib/sync-manager.ts @@ -1,4 +1,5 @@ import axios from 'axios'; +import type { UUID } from '@/types/uuid'; import { uuidv7 } from 'uuidv7'; import { db } from './dexie-db'; @@ -10,8 +11,8 @@ export type StoreName = | 'transactions'; export interface IndexedDBRecord { - id: number | string; - user_id?: number | null; + id: UUID; + user_id?: UUID | null; created_at: string; updated_at: string; [key: string]: any; @@ -180,10 +181,7 @@ export class SyncManager { data: Omit, ): Promise { const timestamp = new Date().toISOString(); - - // Use UUID v7 for transactions, numeric IDs for other entities - const id = - this.options.storeName === 'transactions' ? uuidv7() : Date.now(); + const id = uuidv7(); const record = { ...data, @@ -205,7 +203,7 @@ export class SyncManager { } async updateLocal( - id: number, + id: UUID, data: Partial, ): Promise { const table = db[this.options.storeName]; @@ -233,7 +231,7 @@ export class SyncManager { }); } - async deleteLocal(id: number): Promise { + async deleteLocal(id: UUID): Promise { const timestamp = new Date().toISOString(); const table = db[this.options.storeName]; await table.delete(id); @@ -250,7 +248,7 @@ export class SyncManager { return (await table.toArray()) as T[]; } - async getById(id: number): Promise { + async getById(id: UUID): Promise { const table = db[this.options.storeName]; return ((await table.get(id)) as T) || null; } diff --git a/resources/js/services/account-sync.ts b/resources/js/services/account-sync.ts index 7c1dc46d..7f5e039e 100644 --- a/resources/js/services/account-sync.ts +++ b/resources/js/services/account-sync.ts @@ -1,5 +1,6 @@ import { SyncManager } from '@/lib/sync-manager'; import type { Account } from '@/types/account'; +import type { UUID } from '@/types/uuid'; class AccountSyncService { private syncManager: SyncManager; @@ -19,7 +20,7 @@ class AccountSyncService { return await this.syncManager.getAll(); } - async getById(id: number): Promise { + async getById(id: UUID): Promise { return await this.syncManager.getById(id); } @@ -27,11 +28,11 @@ class AccountSyncService { return await this.syncManager.createLocal(data as any); } - async update(id: number, data: Partial): Promise { + async update(id: UUID, data: Partial): Promise { await this.syncManager.updateLocal(id, data); } - async delete(id: number): Promise { + async delete(id: UUID): Promise { await this.syncManager.deleteLocal(id); } diff --git a/resources/js/services/automation-rule-sync.ts b/resources/js/services/automation-rule-sync.ts index ef89518c..a5bf18f2 100644 --- a/resources/js/services/automation-rule-sync.ts +++ b/resources/js/services/automation-rule-sync.ts @@ -1,5 +1,6 @@ import { SyncManager } from '@/lib/sync-manager'; import type { AutomationRule } from '@/types/automation-rule'; +import type { UUID } from '@/types/uuid'; class AutomationRuleSyncService { private syncManager: SyncManager; @@ -26,7 +27,7 @@ class AutomationRuleSyncService { })); } - async getById(id: number): Promise { + async getById(id: UUID): Promise { const rule = await this.syncManager.getById(id); if (!rule) { return null; @@ -43,18 +44,18 @@ class AutomationRuleSyncService { async create(data: Omit): Promise { return await this.syncManager.createLocal( data as Omit & { - id?: number; + id?: UUID; created_at?: string; updated_at?: string; }, ); } - async update(id: number, data: Partial): Promise { + async update(id: UUID, data: Partial): Promise { await this.syncManager.updateLocal(id, data); } - async delete(id: number): Promise { + async delete(id: UUID): Promise { await this.syncManager.deleteLocal(id); } diff --git a/resources/js/services/bank-sync.ts b/resources/js/services/bank-sync.ts index 22f866cc..12546ad9 100644 --- a/resources/js/services/bank-sync.ts +++ b/resources/js/services/bank-sync.ts @@ -1,5 +1,6 @@ import { SyncManager } from '@/lib/sync-manager'; import type { Bank } from '@/types/account'; +import type { UUID } from '@/types/uuid'; class BankSyncService { private syncManager: SyncManager; @@ -19,7 +20,7 @@ class BankSyncService { return await this.syncManager.getAll(); } - async getById(id: number): Promise { + async getById(id: UUID): Promise { return await this.syncManager.getById(id); } diff --git a/resources/js/services/category-sync.ts b/resources/js/services/category-sync.ts index 5e45039f..c0dfaa46 100644 --- a/resources/js/services/category-sync.ts +++ b/resources/js/services/category-sync.ts @@ -1,5 +1,6 @@ import { SyncManager } from '@/lib/sync-manager'; import type { Category } from '@/types/category'; +import type { UUID } from '@/types/uuid'; class CategorySyncService { private syncManager: SyncManager; @@ -19,7 +20,7 @@ class CategorySyncService { return await this.syncManager.getAll(); } - async getById(id: number): Promise { + async getById(id: UUID): Promise { return await this.syncManager.getById(id); } @@ -27,11 +28,11 @@ class CategorySyncService { return await this.syncManager.createLocal(data as any); } - async update(id: number, data: Partial): Promise { + async update(id: UUID, data: Partial): Promise { await this.syncManager.updateLocal(id, data); } - async delete(id: number): Promise { + async delete(id: UUID): Promise { await this.syncManager.deleteLocal(id); } diff --git a/resources/js/services/transaction-sync.ts b/resources/js/services/transaction-sync.ts index fddc2dce..718286ff 100644 --- a/resources/js/services/transaction-sync.ts +++ b/resources/js/services/transaction-sync.ts @@ -2,13 +2,14 @@ import { encrypt, importKey } from '@/lib/crypto'; import { db } from '@/lib/dexie-db'; import { getStoredKey } from '@/lib/key-storage'; import { SyncManager } from '@/lib/sync-manager'; +import type { UUID } from '@/types/uuid'; import { uuidv7 } from 'uuidv7'; export interface Transaction { - id: string; - user_id: number; - account_id: number; - category_id: number | null; + id: UUID; + user_id: UUID; + account_id: UUID; + category_id: UUID | null; description: string; description_iv: string; transaction_date: string; @@ -38,11 +39,11 @@ class TransactionSyncService { return await this.syncManager.getAll(); } - async getById(id: string): Promise { + async getById(id: UUID): Promise { return (await db.transactions.get(id)) || null; } - async getByAccountId(accountId: number): Promise { + async getByAccountId(accountId: UUID): Promise { try { const allTransactions = await this.getAll(); return allTransactions.filter((t) => t.account_id === accountId); diff --git a/resources/js/types/account.ts b/resources/js/types/account.ts index 4f391c03..52ce278c 100644 --- a/resources/js/types/account.ts +++ b/resources/js/types/account.ts @@ -1,3 +1,5 @@ +import { UUID } from './uuid'; + export const ACCOUNT_TYPES = [ 'checking', 'credit_card', @@ -24,13 +26,14 @@ export const CURRENCY_OPTIONS = [ export type CurrencyCode = (typeof CURRENCY_OPTIONS)[number]; export interface Bank { - id: number; + id: UUID; + user_id: UUID | null; name: string; logo: string | null; } export interface Account { - id: number; + id: UUID; name: string; name_iv: string; bank: Bank; @@ -39,8 +42,8 @@ export interface Account { } export interface AccountBalance { - id: string; - account_id: number; + id: UUID; + account_id: UUID; balance_date: string; balance: number; created_at: string; diff --git a/resources/js/types/automation-rule.ts b/resources/js/types/automation-rule.ts index 9b1da02e..d686f352 100644 --- a/resources/js/types/automation-rule.ts +++ b/resources/js/types/automation-rule.ts @@ -1,12 +1,13 @@ import type { Category } from './category'; +import { UUID } from './uuid'; export interface AutomationRule { - id: number; - user_id: number; + id: UUID; + user_id: UUID; title: string; priority: number; rules_json: Record; - action_category_id: number | null; + action_category_id: UUID | null; action_note: string | null; action_note_iv: string | null; category?: Category; diff --git a/resources/js/types/category.ts b/resources/js/types/category.ts index b16d1006..3f28ee1f 100644 --- a/resources/js/types/category.ts +++ b/resources/js/types/category.ts @@ -1,3 +1,5 @@ +import { UUID } from './uuid'; + export const CATEGORY_ICONS = [ 'AlertCircle', 'AlertTriangle', @@ -71,7 +73,7 @@ export const CATEGORY_COLORS = [ export type CategoryColor = (typeof CATEGORY_COLORS)[number]; export interface Category { - id: number; + id: UUID; name: string; icon: CategoryIcon; color: CategoryColor; diff --git a/resources/js/types/index.d.ts b/resources/js/types/index.d.ts index 2f10844c..9ba43c1a 100644 --- a/resources/js/types/index.d.ts +++ b/resources/js/types/index.d.ts @@ -1,5 +1,6 @@ import { InertiaLinkProps } from '@inertiajs/react'; import { LucideIcon } from 'lucide-react'; +import { UUID } from './uuid'; export interface Auth { user: User; @@ -31,7 +32,7 @@ export interface SharedData { } export interface User { - id: number; + id: UUID; name: string; email: string; avatar?: string; @@ -39,5 +40,5 @@ export interface User { two_factor_enabled?: boolean; created_at: string; updated_at: string; - [key: string]: unknown; // This allows for additional properties... + [key: string]: unknown; } diff --git a/resources/js/types/transaction.ts b/resources/js/types/transaction.ts index 4e6e654b..63efb06a 100644 --- a/resources/js/types/transaction.ts +++ b/resources/js/types/transaction.ts @@ -1,11 +1,12 @@ import { type Account, type Bank } from './account'; import { type Category } from './category'; +import { UUID } from './uuid'; export interface Transaction { - id: string; - user_id: number; - account_id: number; - category_id: number | null; + id: UUID; + user_id: UUID; + account_id: UUID; + category_id: UUID | null; description: string; description_iv: string; transaction_date: string; @@ -30,7 +31,7 @@ export interface TransactionFilters { dateTo: Date | null; amountMin: number | null; amountMax: number | null; - categoryIds: number[]; - accountIds: number[]; + categoryIds: UUID[]; + accountIds: UUID[]; searchText: string; } diff --git a/resources/js/types/uuid.ts b/resources/js/types/uuid.ts new file mode 100644 index 00000000..4cd7fd1a --- /dev/null +++ b/resources/js/types/uuid.ts @@ -0,0 +1,2 @@ +export type UUID = string; +