mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2025-12-30 17:47:24 +01:00
Fix wipe inventory action to use correct onclose
This commit is contained in:
@@ -8,7 +8,7 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/sysadminsmedia/homebox/backend v0.0.0-20251228052820-4557df86eddb h1:nRu1qr3gceoIIDJolCRnd/Eo5VLAMoH9CYnyKCCVBuA=
|
||||
github.com/sysadminsmedia/homebox/backend v0.0.0-20251228052820-4557df86eddb/go.mod h1:9zHHw5TNttw5Kn4Wks+SxwXmJPz6PgGNbnB4BtF1Z4c=
|
||||
github.com/sysadminsmedia/homebox/backend v0.0.0-20251228163253-2bd6ff580a7f h1:+5m3FRu/Ja3kH2XgFn0GYCWOKIe4O+7PbLawLXvb4gA=
|
||||
github.com/sysadminsmedia/homebox/backend v0.0.0-20251228163253-2bd6ff580a7f/go.mod h1:9zHHw5TNttw5Kn4Wks+SxwXmJPz6PgGNbnB4BtF1Z4c=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -810,6 +810,22 @@ func (e *ItemsRepository) DeleteByGroup(ctx context.Context, gid, id uuid.UUID)
|
||||
}
|
||||
|
||||
func (e *ItemsRepository) WipeInventory(ctx context.Context, gid uuid.UUID, wipeLabels bool, wipeLocations bool, wipeMaintenance bool) (int, error) {
|
||||
deleted := 0
|
||||
|
||||
// Wipe maintenance records if requested
|
||||
// IMPORTANT: Must delete maintenance records BEFORE items since they are linked to items
|
||||
if wipeMaintenance {
|
||||
maintenanceCount, err := e.db.MaintenanceEntry.Delete().
|
||||
Where(maintenanceentry.HasItemWith(item.HasGroupWith(group.ID(gid)))).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to delete maintenance entries during wipe inventory")
|
||||
} else {
|
||||
log.Info().Int("count", maintenanceCount).Msg("deleted maintenance entries during wipe inventory")
|
||||
deleted += maintenanceCount
|
||||
}
|
||||
}
|
||||
|
||||
// Get all items for the group
|
||||
items, err := e.db.Item.Query().
|
||||
Where(item.HasGroupWith(group.ID(gid))).
|
||||
@@ -819,7 +835,6 @@ func (e *ItemsRepository) WipeInventory(ctx context.Context, gid uuid.UUID, wipe
|
||||
return 0, err
|
||||
}
|
||||
|
||||
deleted := 0
|
||||
// Delete each item with its attachments
|
||||
// Note: We manually delete attachments and items instead of calling DeleteByGroup
|
||||
// to continue processing remaining items even if some deletions fail
|
||||
@@ -872,20 +887,6 @@ func (e *ItemsRepository) WipeInventory(ctx context.Context, gid uuid.UUID, wipe
|
||||
}
|
||||
}
|
||||
|
||||
// Wipe maintenance records if requested
|
||||
if wipeMaintenance {
|
||||
// Maintenance entries are linked to items, so we query by items in the group
|
||||
maintenanceCount, err := e.db.MaintenanceEntry.Delete().
|
||||
Where(maintenanceentry.HasItemWith(item.HasGroupWith(group.ID(gid)))).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to delete maintenance entries during wipe inventory")
|
||||
} else {
|
||||
log.Info().Int("count", maintenanceCount).Msg("deleted maintenance entries during wipe inventory")
|
||||
deleted += maintenanceCount
|
||||
}
|
||||
}
|
||||
|
||||
e.publishMutationEvent(gid)
|
||||
return deleted, nil
|
||||
}
|
||||
|
||||
@@ -398,4 +398,161 @@ func TestItemsRepository_DeleteByGroupWithAttachments(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestItemsRepository_WipeInventory(t *testing.T) {
|
||||
// Create test data: items, labels, locations, and maintenance entries
|
||||
|
||||
// Create locations
|
||||
loc1, err := tRepos.Locations.Create(context.Background(), tGroup.ID, LocationCreate{
|
||||
Name: "Test Location 1",
|
||||
Description: "Test location for wipe test",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
loc2, err := tRepos.Locations.Create(context.Background(), tGroup.ID, LocationCreate{
|
||||
Name: "Test Location 2",
|
||||
Description: "Another test location",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create labels
|
||||
label1, err := tRepos.Labels.Create(context.Background(), tGroup.ID, LabelCreate{
|
||||
Name: "Test Label 1",
|
||||
Description: "Test label for wipe test",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
label2, err := tRepos.Labels.Create(context.Background(), tGroup.ID, LabelCreate{
|
||||
Name: "Test Label 2",
|
||||
Description: "Another test label",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create items
|
||||
item1, err := tRepos.Items.Create(context.Background(), tGroup.ID, ItemCreate{
|
||||
Name: "Test Item 1",
|
||||
Description: "Test item for wipe test",
|
||||
LocationID: loc1.ID,
|
||||
LabelIDs: []uuid.UUID{label1.ID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
item2, err := tRepos.Items.Create(context.Background(), tGroup.ID, ItemCreate{
|
||||
Name: "Test Item 2",
|
||||
Description: "Another test item",
|
||||
LocationID: loc2.ID,
|
||||
LabelIDs: []uuid.UUID{label2.ID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create maintenance entries for items
|
||||
_, err = tRepos.MaintEntry.Create(context.Background(), item1.ID, MaintenanceEntryCreate{
|
||||
CompletedDate: types.DateFromTime(time.Now()),
|
||||
Name: "Test Maintenance 1",
|
||||
Description: "Test maintenance entry",
|
||||
Cost: 100.0,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = tRepos.MaintEntry.Create(context.Background(), item2.ID, MaintenanceEntryCreate{
|
||||
CompletedDate: types.DateFromTime(time.Now()),
|
||||
Name: "Test Maintenance 2",
|
||||
Description: "Another test maintenance entry",
|
||||
Cost: 200.0,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test 1: Wipe inventory with all options enabled
|
||||
t.Run("wipe all including labels, locations, and maintenance", func(t *testing.T) {
|
||||
deleted, err := tRepos.Items.WipeInventory(context.Background(), tGroup.ID, true, true, true)
|
||||
require.NoError(t, err)
|
||||
assert.Greater(t, deleted, 0, "Should have deleted at least some entities")
|
||||
|
||||
// Verify items are deleted
|
||||
_, err = tRepos.Items.GetOneByGroup(context.Background(), tGroup.ID, item1.ID)
|
||||
require.Error(t, err, "Item 1 should be deleted")
|
||||
|
||||
_, err = tRepos.Items.GetOneByGroup(context.Background(), tGroup.ID, item2.ID)
|
||||
require.Error(t, err, "Item 2 should be deleted")
|
||||
|
||||
// Verify maintenance entries are deleted (query by item ID, should return empty)
|
||||
maint1List, err := tRepos.MaintEntry.GetMaintenanceByItemID(context.Background(), tGroup.ID, item1.ID, MaintenanceFilters{})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, maint1List, "Maintenance entry 1 should be deleted")
|
||||
|
||||
maint2List, err := tRepos.MaintEntry.GetMaintenanceByItemID(context.Background(), tGroup.ID, item2.ID, MaintenanceFilters{})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, maint2List, "Maintenance entry 2 should be deleted")
|
||||
|
||||
// Verify labels are deleted
|
||||
_, err = tRepos.Labels.GetOneByGroup(context.Background(), tGroup.ID, label1.ID)
|
||||
require.Error(t, err, "Label 1 should be deleted")
|
||||
|
||||
_, err = tRepos.Labels.GetOneByGroup(context.Background(), tGroup.ID, label2.ID)
|
||||
require.Error(t, err, "Label 2 should be deleted")
|
||||
|
||||
// Verify locations are deleted
|
||||
_, err = tRepos.Locations.Get(context.Background(), loc1.ID)
|
||||
require.Error(t, err, "Location 1 should be deleted")
|
||||
|
||||
_, err = tRepos.Locations.Get(context.Background(), loc2.ID)
|
||||
require.Error(t, err, "Location 2 should be deleted")
|
||||
})
|
||||
}
|
||||
|
||||
func TestItemsRepository_WipeInventory_OnlyItems(t *testing.T) {
|
||||
// Create test data
|
||||
loc, err := tRepos.Locations.Create(context.Background(), tGroup.ID, LocationCreate{
|
||||
Name: "Test Location",
|
||||
Description: "Test location for wipe test",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
label, err := tRepos.Labels.Create(context.Background(), tGroup.ID, LabelCreate{
|
||||
Name: "Test Label",
|
||||
Description: "Test label for wipe test",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
item, err := tRepos.Items.Create(context.Background(), tGroup.ID, ItemCreate{
|
||||
Name: "Test Item",
|
||||
Description: "Test item for wipe test",
|
||||
LocationID: loc.ID,
|
||||
LabelIDs: []uuid.UUID{label.ID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = tRepos.MaintEntry.Create(context.Background(), item.ID, MaintenanceEntryCreate{
|
||||
CompletedDate: types.DateFromTime(time.Now()),
|
||||
Name: "Test Maintenance",
|
||||
Description: "Test maintenance entry",
|
||||
Cost: 100.0,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test: Wipe inventory with only items (no labels, locations, or maintenance)
|
||||
deleted, err := tRepos.Items.WipeInventory(context.Background(), tGroup.ID, false, false, false)
|
||||
require.NoError(t, err)
|
||||
assert.Greater(t, deleted, 0, "Should have deleted at least the item")
|
||||
|
||||
// Verify item is deleted
|
||||
_, err = tRepos.Items.GetOneByGroup(context.Background(), tGroup.ID, item.ID)
|
||||
require.Error(t, err, "Item should be deleted")
|
||||
|
||||
// Verify maintenance entry is deleted due to cascade
|
||||
maintList, err := tRepos.MaintEntry.GetMaintenanceByItemID(context.Background(), tGroup.ID, item.ID, MaintenanceFilters{})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, maintList, "Maintenance entry should be cascade deleted with item")
|
||||
|
||||
// Verify label still exists
|
||||
_, err = tRepos.Labels.GetOneByGroup(context.Background(), tGroup.ID, label.ID)
|
||||
require.NoError(t, err, "Label should still exist")
|
||||
|
||||
// Verify location still exists
|
||||
_, err = tRepos.Locations.Get(context.Background(), loc.ID)
|
||||
require.NoError(t, err, "Location should still exist")
|
||||
|
||||
// Cleanup
|
||||
_ = tRepos.Labels.DeleteByGroup(context.Background(), tGroup.ID, label.ID)
|
||||
_ = tRepos.Locations.delete(context.Background(), loc.ID)
|
||||
}
|
||||
|
||||
|
||||
194
backend/internal/data/repo/repo_wipe_integration_test.go
Normal file
194
backend/internal/data/repo/repo_wipe_integration_test.go
Normal file
@@ -0,0 +1,194 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/types"
|
||||
)
|
||||
|
||||
// TestWipeInventory_Integration tests the complete wipe inventory flow
|
||||
func TestWipeInventory_Integration(t *testing.T) {
|
||||
// Create test data: locations, labels, items with maintenance
|
||||
|
||||
// 1. Create locations
|
||||
loc1, err := tRepos.Locations.Create(context.Background(), tGroup.ID, LocationCreate{
|
||||
Name: "Test Garage",
|
||||
Description: "Garage location",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
loc2, err := tRepos.Locations.Create(context.Background(), tGroup.ID, LocationCreate{
|
||||
Name: "Test Basement",
|
||||
Description: "Basement location",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// 2. Create labels
|
||||
label1, err := tRepos.Labels.Create(context.Background(), tGroup.ID, LabelCreate{
|
||||
Name: "Test Electronics",
|
||||
Description: "Electronics label",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
label2, err := tRepos.Labels.Create(context.Background(), tGroup.ID, LabelCreate{
|
||||
Name: "Test Tools",
|
||||
Description: "Tools label",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// 3. Create items
|
||||
item1, err := tRepos.Items.Create(context.Background(), tGroup.ID, ItemCreate{
|
||||
Name: "Test Laptop",
|
||||
Description: "Work laptop",
|
||||
LocationID: loc1.ID,
|
||||
LabelIDs: []uuid.UUID{label1.ID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
item2, err := tRepos.Items.Create(context.Background(), tGroup.ID, ItemCreate{
|
||||
Name: "Test Drill",
|
||||
Description: "Power drill",
|
||||
LocationID: loc2.ID,
|
||||
LabelIDs: []uuid.UUID{label2.ID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
item3, err := tRepos.Items.Create(context.Background(), tGroup.ID, ItemCreate{
|
||||
Name: "Test Monitor",
|
||||
Description: "Computer monitor",
|
||||
LocationID: loc1.ID,
|
||||
LabelIDs: []uuid.UUID{label1.ID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// 4. Create maintenance entries
|
||||
_, err = tRepos.MaintEntry.Create(context.Background(), item1.ID, MaintenanceEntryCreate{
|
||||
CompletedDate: types.DateFromTime(time.Now()),
|
||||
Name: "Laptop cleaning",
|
||||
Description: "Cleaned keyboard and screen",
|
||||
Cost: 0,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = tRepos.MaintEntry.Create(context.Background(), item2.ID, MaintenanceEntryCreate{
|
||||
CompletedDate: types.DateFromTime(time.Now()),
|
||||
Name: "Drill maintenance",
|
||||
Description: "Oiled motor",
|
||||
Cost: 5.00,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = tRepos.MaintEntry.Create(context.Background(), item3.ID, MaintenanceEntryCreate{
|
||||
CompletedDate: types.DateFromTime(time.Now()),
|
||||
Name: "Monitor calibration",
|
||||
Description: "Color calibration",
|
||||
Cost: 0,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// 5. Verify items exist
|
||||
allItems, err := tRepos.Items.GetAll(context.Background(), tGroup.ID)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, len(allItems), 3, "Should have at least 3 items")
|
||||
|
||||
// 6. Verify maintenance entries exist
|
||||
maint1List, err := tRepos.MaintEntry.GetMaintenanceByItemID(context.Background(), tGroup.ID, item1.ID, MaintenanceFilters{})
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, maint1List, "Item 1 should have maintenance records")
|
||||
|
||||
maint2List, err := tRepos.MaintEntry.GetMaintenanceByItemID(context.Background(), tGroup.ID, item2.ID, MaintenanceFilters{})
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, maint2List, "Item 2 should have maintenance records")
|
||||
|
||||
// 7. Test wipe inventory with all options enabled
|
||||
deleted, err := tRepos.Items.WipeInventory(context.Background(), tGroup.ID, true, true, true)
|
||||
require.NoError(t, err)
|
||||
assert.Greater(t, deleted, 0, "Should have deleted entities")
|
||||
|
||||
// 8. Verify all items are deleted
|
||||
allItemsAfter, err := tRepos.Items.GetAll(context.Background(), tGroup.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, len(allItemsAfter), "All items should be deleted")
|
||||
|
||||
// 9. Verify maintenance entries are deleted
|
||||
maint1After, err := tRepos.MaintEntry.GetMaintenanceByItemID(context.Background(), tGroup.ID, item1.ID, MaintenanceFilters{})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, maint1After, "Item 1 maintenance records should be deleted")
|
||||
|
||||
// 10. Verify labels are deleted
|
||||
_, err = tRepos.Labels.GetOneByGroup(context.Background(), tGroup.ID, label1.ID)
|
||||
require.Error(t, err, "Label 1 should be deleted")
|
||||
|
||||
_, err = tRepos.Labels.GetOneByGroup(context.Background(), tGroup.ID, label2.ID)
|
||||
require.Error(t, err, "Label 2 should be deleted")
|
||||
|
||||
// 11. Verify locations are deleted
|
||||
_, err = tRepos.Locations.Get(context.Background(), loc1.ID)
|
||||
require.Error(t, err, "Location 1 should be deleted")
|
||||
|
||||
_, err = tRepos.Locations.Get(context.Background(), loc2.ID)
|
||||
require.Error(t, err, "Location 2 should be deleted")
|
||||
}
|
||||
|
||||
// TestWipeInventory_SelectiveWipe tests wiping only certain entity types
|
||||
func TestWipeInventory_SelectiveWipe(t *testing.T) {
|
||||
// Create test data
|
||||
loc, err := tRepos.Locations.Create(context.Background(), tGroup.ID, LocationCreate{
|
||||
Name: "Test Office",
|
||||
Description: "Office location",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
label, err := tRepos.Labels.Create(context.Background(), tGroup.ID, LabelCreate{
|
||||
Name: "Test Important",
|
||||
Description: "Important label",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
item, err := tRepos.Items.Create(context.Background(), tGroup.ID, ItemCreate{
|
||||
Name: "Test Computer",
|
||||
Description: "Desktop computer",
|
||||
LocationID: loc.ID,
|
||||
LabelIDs: []uuid.UUID{label.ID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = tRepos.MaintEntry.Create(context.Background(), item.ID, MaintenanceEntryCreate{
|
||||
CompletedDate: types.DateFromTime(time.Now()),
|
||||
Name: "System update",
|
||||
Description: "OS update",
|
||||
Cost: 0,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test: Wipe only items (keep labels and locations)
|
||||
deleted, err := tRepos.Items.WipeInventory(context.Background(), tGroup.ID, false, false, false)
|
||||
require.NoError(t, err)
|
||||
assert.Greater(t, deleted, 0, "Should have deleted at least items")
|
||||
|
||||
// Verify item is deleted
|
||||
_, err = tRepos.Items.GetOneByGroup(context.Background(), tGroup.ID, item.ID)
|
||||
require.Error(t, err, "Item should be deleted")
|
||||
|
||||
// Verify maintenance is cascade deleted
|
||||
maintList, err := tRepos.MaintEntry.GetMaintenanceByItemID(context.Background(), tGroup.ID, item.ID, MaintenanceFilters{})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, maintList, "Maintenance should be cascade deleted")
|
||||
|
||||
// Verify label still exists
|
||||
_, err = tRepos.Labels.GetOneByGroup(context.Background(), tGroup.ID, label.ID)
|
||||
require.NoError(t, err, "Label should still exist")
|
||||
|
||||
// Verify location still exists
|
||||
_, err = tRepos.Locations.Get(context.Background(), loc.ID)
|
||||
require.NoError(t, err, "Location should still exist")
|
||||
|
||||
// Cleanup
|
||||
_ = tRepos.Labels.DeleteByGroup(context.Background(), tGroup.ID, label.ID)
|
||||
_ = tRepos.Locations.delete(context.Background(), loc.ID)
|
||||
}
|
||||
@@ -83,24 +83,12 @@
|
||||
const wipeLocations = ref(false);
|
||||
const wipeMaintenance = ref(false);
|
||||
|
||||
let onCloseCallback:
|
||||
| ((result?: { wipeLabels: boolean; wipeLocations: boolean; wipeMaintenance: boolean } | undefined) => void)
|
||||
| undefined;
|
||||
|
||||
registerOpenDialogCallback(
|
||||
DialogID.WipeInventory,
|
||||
(params?: {
|
||||
onClose?: (
|
||||
result?: { wipeLabels: boolean; wipeLocations: boolean; wipeMaintenance: boolean } | undefined
|
||||
) => void;
|
||||
}) => {
|
||||
dialog.value = true;
|
||||
wipeLabels.value = false;
|
||||
wipeLocations.value = false;
|
||||
wipeMaintenance.value = false;
|
||||
onCloseCallback = params?.onClose;
|
||||
}
|
||||
);
|
||||
registerOpenDialogCallback(DialogID.WipeInventory, () => {
|
||||
dialog.value = true;
|
||||
wipeLabels.value = false;
|
||||
wipeLocations.value = false;
|
||||
wipeMaintenance.value = false;
|
||||
});
|
||||
|
||||
watch(
|
||||
dialog,
|
||||
@@ -123,7 +111,6 @@
|
||||
function close() {
|
||||
dialog.value = false;
|
||||
closeDialog(DialogID.WipeInventory, undefined);
|
||||
onCloseCallback?.(undefined);
|
||||
}
|
||||
|
||||
function confirm() {
|
||||
@@ -134,6 +121,5 @@
|
||||
wipeMaintenance: wipeMaintenance.value,
|
||||
};
|
||||
closeDialog(DialogID.WipeInventory, result);
|
||||
onCloseCallback?.(result);
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user