Migrate ID's to UUIDv7
This commit is contained in:
parent
dcb78d2252
commit
9d57804dc5
|
|
@ -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());
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -29,9 +29,4 @@ class AccountBalance extends Model
|
|||
{
|
||||
return $this->belongsTo(Account::class);
|
||||
}
|
||||
|
||||
public function newUniqueId(): string
|
||||
{
|
||||
return (string) \Illuminate\Support\Str::uuid7();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -48,9 +48,4 @@ class Transaction extends Model
|
|||
{
|
||||
return $this->belongsTo(Category::class);
|
||||
}
|
||||
|
||||
public function newUniqueId(): string
|
||||
{
|
||||
return (string) \Illuminate\Support\Str::uuid7();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,329 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): 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('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');
|
||||
}
|
||||
};
|
||||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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) =>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
} = {
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ export function EditTransactionDialog({
|
|||
const [notes, setNotes] = useState('');
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [decryptedAccountNames, setDecryptedAccountNames] = useState<
|
||||
Map<number, string>
|
||||
Map<string, string>
|
||||
>(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<number, string>();
|
||||
const decryptedNames = new Map<string, string>();
|
||||
|
||||
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 (
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="flex flex-col gap-6">
|
||||
<RadioGroup
|
||||
value={selectedAccountId?.toString()}
|
||||
onValueChange={(value) => onAccountSelect(Number(value))}
|
||||
value={selectedAccountId}
|
||||
onValueChange={(value) => onAccountSelect(value)}
|
||||
>
|
||||
<div className="space-y-3">
|
||||
{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"
|
||||
>
|
||||
<RadioGroupItem
|
||||
value={account.id.toString()}
|
||||
value={account.id}
|
||||
id={`account-${account.id}`}
|
||||
/>
|
||||
{account.bank.logo ? (
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ const db = new Dexie('whisper_money') as Dexie & {
|
|||
pending_changes: EntityTable<PendingChange, 'id'>;
|
||||
};
|
||||
|
||||
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',
|
||||
|
|
|
|||
|
|
@ -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<T, 'id' | 'created_at' | 'updated_at'>,
|
||||
): Promise<T> {
|
||||
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<T extends IndexedDBRecord>(
|
||||
id: number,
|
||||
id: UUID,
|
||||
data: Partial<T>,
|
||||
): Promise<void> {
|
||||
const table = db[this.options.storeName];
|
||||
|
|
@ -233,7 +231,7 @@ export class SyncManager {
|
|||
});
|
||||
}
|
||||
|
||||
async deleteLocal(id: number): Promise<void> {
|
||||
async deleteLocal(id: UUID): Promise<void> {
|
||||
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<T extends IndexedDBRecord>(id: number): Promise<T | null> {
|
||||
async getById<T extends IndexedDBRecord>(id: UUID): Promise<T | null> {
|
||||
const table = db[this.options.storeName];
|
||||
return ((await table.get(id)) as T) || null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Account>();
|
||||
}
|
||||
|
||||
async getById(id: number): Promise<Account | null> {
|
||||
async getById(id: UUID): Promise<Account | null> {
|
||||
return await this.syncManager.getById<Account>(id);
|
||||
}
|
||||
|
||||
|
|
@ -27,11 +28,11 @@ class AccountSyncService {
|
|||
return await this.syncManager.createLocal<Account>(data as any);
|
||||
}
|
||||
|
||||
async update(id: number, data: Partial<Account>): Promise<void> {
|
||||
async update(id: UUID, data: Partial<Account>): Promise<void> {
|
||||
await this.syncManager.updateLocal<Account>(id, data);
|
||||
}
|
||||
|
||||
async delete(id: number): Promise<void> {
|
||||
async delete(id: UUID): Promise<void> {
|
||||
await this.syncManager.deleteLocal(id);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<AutomationRule | null> {
|
||||
async getById(id: UUID): Promise<AutomationRule | null> {
|
||||
const rule = await this.syncManager.getById<AutomationRule>(id);
|
||||
if (!rule) {
|
||||
return null;
|
||||
|
|
@ -43,18 +44,18 @@ class AutomationRuleSyncService {
|
|||
async create(data: Omit<AutomationRule, 'id'>): Promise<AutomationRule> {
|
||||
return await this.syncManager.createLocal<AutomationRule>(
|
||||
data as Omit<AutomationRule, 'id'> & {
|
||||
id?: number;
|
||||
id?: UUID;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async update(id: number, data: Partial<AutomationRule>): Promise<void> {
|
||||
async update(id: UUID, data: Partial<AutomationRule>): Promise<void> {
|
||||
await this.syncManager.updateLocal<AutomationRule>(id, data);
|
||||
}
|
||||
|
||||
async delete(id: number): Promise<void> {
|
||||
async delete(id: UUID): Promise<void> {
|
||||
await this.syncManager.deleteLocal(id);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Bank>();
|
||||
}
|
||||
|
||||
async getById(id: number): Promise<Bank | null> {
|
||||
async getById(id: UUID): Promise<Bank | null> {
|
||||
return await this.syncManager.getById<Bank>(id);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Category>();
|
||||
}
|
||||
|
||||
async getById(id: number): Promise<Category | null> {
|
||||
async getById(id: UUID): Promise<Category | null> {
|
||||
return await this.syncManager.getById<Category>(id);
|
||||
}
|
||||
|
||||
|
|
@ -27,11 +28,11 @@ class CategorySyncService {
|
|||
return await this.syncManager.createLocal<Category>(data as any);
|
||||
}
|
||||
|
||||
async update(id: number, data: Partial<Category>): Promise<void> {
|
||||
async update(id: UUID, data: Partial<Category>): Promise<void> {
|
||||
await this.syncManager.updateLocal<Category>(id, data);
|
||||
}
|
||||
|
||||
async delete(id: number): Promise<void> {
|
||||
async delete(id: UUID): Promise<void> {
|
||||
await this.syncManager.deleteLocal(id);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Transaction>();
|
||||
}
|
||||
|
||||
async getById(id: string): Promise<Transaction | null> {
|
||||
async getById(id: UUID): Promise<Transaction | null> {
|
||||
return (await db.transactions.get(id)) || null;
|
||||
}
|
||||
|
||||
async getByAccountId(accountId: number): Promise<Transaction[]> {
|
||||
async getByAccountId(accountId: UUID): Promise<Transaction[]> {
|
||||
try {
|
||||
const allTransactions = await this.getAll();
|
||||
return allTransactions.filter((t) => t.account_id === accountId);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<string, any>;
|
||||
action_category_id: number | null;
|
||||
action_category_id: UUID | null;
|
||||
action_note: string | null;
|
||||
action_note_iv: string | null;
|
||||
category?: Category;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
export type UUID = string;
|
||||
|
||||
Loading…
Reference in New Issue