Implement admin functionality for merging duplicate records with error handling and logging
This commit is contained in:
parent
b3e7d04b86
commit
4a60782f89
|
|
@ -27,19 +27,28 @@ export const useAuthorization = () => {
|
||||||
groups: []
|
groups: []
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Create a loading state
|
||||||
|
const isLoading = ref(true);
|
||||||
|
|
||||||
// Function to sync auth state from nuxtApp payload
|
// Function to sync auth state from nuxtApp payload
|
||||||
const syncAuthState = () => {
|
const syncAuthState = () => {
|
||||||
const payloadAuthState = nuxtApp.payload.data?.authState as AuthState;
|
try {
|
||||||
if (payloadAuthState) {
|
// Safely check if payload data exists
|
||||||
authState.value = payloadAuthState;
|
if (nuxtApp.payload && nuxtApp.payload.data && nuxtApp.payload.data.authState) {
|
||||||
console.log('[useAuthorization] Auth state synced:', {
|
const payloadAuthState = nuxtApp.payload.data.authState as AuthState;
|
||||||
authenticated: payloadAuthState.authenticated,
|
authState.value = payloadAuthState;
|
||||||
groups: payloadAuthState.groups,
|
isLoading.value = false;
|
||||||
user: payloadAuthState.user?.email
|
console.log('[useAuthorization] Auth state synced from payload:', {
|
||||||
});
|
authenticated: payloadAuthState.authenticated,
|
||||||
} else {
|
groups: payloadAuthState.groups,
|
||||||
console.log('[useAuthorization] No auth state found in payload');
|
user: payloadAuthState.user?.email
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[useAuthorization] Error syncing auth state:', error);
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Try to get auth state from API if not in payload
|
// Try to get auth state from API if not in payload
|
||||||
|
|
@ -47,6 +56,7 @@ export const useAuthorization = () => {
|
||||||
try {
|
try {
|
||||||
const sessionData = await $fetch('/api/auth/session') as AuthState;
|
const sessionData = await $fetch('/api/auth/session') as AuthState;
|
||||||
authState.value = sessionData;
|
authState.value = sessionData;
|
||||||
|
isLoading.value = false;
|
||||||
console.log('[useAuthorization] Auth state loaded from API:', {
|
console.log('[useAuthorization] Auth state loaded from API:', {
|
||||||
authenticated: sessionData.authenticated,
|
authenticated: sessionData.authenticated,
|
||||||
groups: sessionData.groups,
|
groups: sessionData.groups,
|
||||||
|
|
@ -57,31 +67,46 @@ export const useAuthorization = () => {
|
||||||
updateAuthState(sessionData);
|
updateAuthState(sessionData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[useAuthorization] Failed to load auth state:', error);
|
console.error('[useAuthorization] Failed to load auth state:', error);
|
||||||
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize auth state
|
// Initialize auth state immediately (not just onMounted)
|
||||||
onMounted(() => {
|
if (process.client) {
|
||||||
syncAuthState();
|
// Try to sync from payload first
|
||||||
|
const synced = syncAuthState();
|
||||||
|
|
||||||
// If no auth state in payload, try to load from API
|
// If not synced from payload, load from API
|
||||||
if (!authState.value.authenticated) {
|
if (!synced) {
|
||||||
loadAuthState();
|
loadAuthState();
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
|
// On server, try to get from payload
|
||||||
|
syncAuthState();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get current user groups from session
|
* Get current user groups from session
|
||||||
*/
|
*/
|
||||||
const getUserGroups = (): string[] => {
|
const getUserGroups = (): string[] => {
|
||||||
return authState.value.groups || [];
|
try {
|
||||||
|
return authState.value?.groups || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[useAuthorization] Error getting user groups:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get current authenticated user
|
* Get current authenticated user
|
||||||
*/
|
*/
|
||||||
const getCurrentUser = (): UserWithGroups | null => {
|
const getCurrentUser = (): UserWithGroups | null => {
|
||||||
return authState.value.user || null;
|
try {
|
||||||
|
return authState.value?.user || null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[useAuthorization] Error getting current user:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -72,117 +72,181 @@ definePageMeta({
|
||||||
|
|
||||||
const { mdAndDown } = useDisplay();
|
const { mdAndDown } = useDisplay();
|
||||||
const { user, logout, authSource } = useUnifiedAuth();
|
const { user, logout, authSource } = useUnifiedAuth();
|
||||||
const { isAdmin, getUserGroups, getCurrentUser } = useAuthorization();
|
const authUtils = useAuthorization();
|
||||||
const tags = usePortalTags();
|
const tags = usePortalTags();
|
||||||
|
|
||||||
const drawer = ref(false);
|
const drawer = ref(false);
|
||||||
|
|
||||||
|
// Safe wrapper for auth functions
|
||||||
|
const safeIsAdmin = () => {
|
||||||
|
try {
|
||||||
|
return authUtils.isAdmin();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Dashboard] Error checking admin status:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const safeGetUserGroups = () => {
|
||||||
|
try {
|
||||||
|
return authUtils.getUserGroups();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Dashboard] Error getting user groups:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const safeGetCurrentUser = () => {
|
||||||
|
try {
|
||||||
|
return authUtils.getCurrentUser();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Dashboard] Error getting current user:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Debug auth state
|
// Debug auth state
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
console.log('[Dashboard] Auth state on mount:', {
|
nextTick(() => {
|
||||||
isAdmin: isAdmin(),
|
console.log('[Dashboard] Auth state on mount:', {
|
||||||
userGroups: getUserGroups(),
|
isAdmin: safeIsAdmin(),
|
||||||
currentUser: getCurrentUser()
|
userGroups: safeGetUserGroups(),
|
||||||
|
currentUser: safeGetCurrentUser()
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const interestMenu = computed(() => {
|
const interestMenu = computed(() => {
|
||||||
const userIsAdmin = isAdmin();
|
try {
|
||||||
const userGroups = getUserGroups();
|
const userIsAdmin = safeIsAdmin();
|
||||||
|
const userGroups = safeGetUserGroups();
|
||||||
console.log('[Dashboard] Computing interest menu - isAdmin:', userIsAdmin, 'groups:', userGroups);
|
|
||||||
|
console.log('[Dashboard] Computing interest menu - isAdmin:', userIsAdmin, 'groups:', userGroups);
|
||||||
const baseMenu = [
|
|
||||||
//{
|
const baseMenu = [
|
||||||
// to: "/dashboard/interest-eoi-queue",
|
//{
|
||||||
// icon: "mdi-tray-full",
|
// to: "/dashboard/interest-eoi-queue",
|
||||||
// title: "EOI Queue",
|
// icon: "mdi-tray-full",
|
||||||
//},
|
// title: "EOI Queue",
|
||||||
{
|
//},
|
||||||
to: "/dashboard/interest-analytics",
|
{
|
||||||
icon: "mdi-view-dashboard",
|
to: "/dashboard/interest-analytics",
|
||||||
title: "Analytics",
|
icon: "mdi-view-dashboard",
|
||||||
},
|
title: "Analytics",
|
||||||
{
|
},
|
||||||
to: "/dashboard/interest-berth-list",
|
{
|
||||||
icon: "mdi-table",
|
to: "/dashboard/interest-berth-list",
|
||||||
title: "Berth List",
|
icon: "mdi-table",
|
||||||
},
|
title: "Berth List",
|
||||||
{
|
},
|
||||||
to: "/dashboard/interest-berth-status",
|
{
|
||||||
icon: "mdi-sail-boat",
|
to: "/dashboard/interest-berth-status",
|
||||||
title: "Berth Status",
|
icon: "mdi-sail-boat",
|
||||||
},
|
title: "Berth Status",
|
||||||
{
|
},
|
||||||
to: "/dashboard/interest-list",
|
{
|
||||||
icon: "mdi-view-list",
|
to: "/dashboard/interest-list",
|
||||||
title: "Interest List",
|
icon: "mdi-view-list",
|
||||||
},
|
title: "Interest List",
|
||||||
{
|
},
|
||||||
to: "/dashboard/interest-status",
|
{
|
||||||
icon: "mdi-account-check",
|
to: "/dashboard/interest-status",
|
||||||
title: "Interest Status",
|
icon: "mdi-account-check",
|
||||||
},
|
title: "Interest Status",
|
||||||
{
|
},
|
||||||
to: "/dashboard/expenses",
|
{
|
||||||
icon: "mdi-receipt",
|
to: "/dashboard/expenses",
|
||||||
title: "Expenses",
|
icon: "mdi-receipt",
|
||||||
},
|
title: "Expenses",
|
||||||
{
|
},
|
||||||
to: "/dashboard/file-browser",
|
{
|
||||||
icon: "mdi-folder",
|
to: "/dashboard/file-browser",
|
||||||
title: "File Browser",
|
icon: "mdi-folder",
|
||||||
},
|
title: "File Browser",
|
||||||
];
|
},
|
||||||
|
];
|
||||||
|
|
||||||
// Add admin menu items if user is admin
|
// Add admin menu items if user is admin
|
||||||
if (userIsAdmin) {
|
if (userIsAdmin) {
|
||||||
console.log('[Dashboard] Adding admin console to interest menu');
|
console.log('[Dashboard] Adding admin console to interest menu');
|
||||||
baseMenu.push({
|
baseMenu.push({
|
||||||
to: "/dashboard/admin",
|
to: "/dashboard/admin",
|
||||||
icon: "mdi-shield-crown",
|
icon: "mdi-shield-crown",
|
||||||
title: "Admin Console",
|
title: "Admin Console",
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseMenu;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Dashboard] Error computing interest menu:', error);
|
||||||
|
// Return basic menu without admin items on error
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
to: "/dashboard/interest-analytics",
|
||||||
|
icon: "mdi-view-dashboard",
|
||||||
|
title: "Analytics",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: "/dashboard/interest-list",
|
||||||
|
icon: "mdi-view-list",
|
||||||
|
title: "Interest List",
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return baseMenu;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultMenu = computed(() => {
|
const defaultMenu = computed(() => {
|
||||||
const userIsAdmin = isAdmin();
|
try {
|
||||||
const userGroups = getUserGroups();
|
const userIsAdmin = safeIsAdmin();
|
||||||
|
const userGroups = safeGetUserGroups();
|
||||||
console.log('[Dashboard] Computing default menu - isAdmin:', userIsAdmin, 'groups:', userGroups);
|
|
||||||
|
console.log('[Dashboard] Computing default menu - isAdmin:', userIsAdmin, 'groups:', userGroups);
|
||||||
const baseMenu = [
|
|
||||||
{
|
const baseMenu = [
|
||||||
to: "/dashboard/site",
|
{
|
||||||
icon: "mdi-view-dashboard",
|
to: "/dashboard/site",
|
||||||
title: "Site Analytics",
|
icon: "mdi-view-dashboard",
|
||||||
},
|
title: "Site Analytics",
|
||||||
{
|
},
|
||||||
to: "/dashboard/data",
|
{
|
||||||
icon: "mdi-finance",
|
to: "/dashboard/data",
|
||||||
title: "Data Analytics",
|
icon: "mdi-finance",
|
||||||
},
|
title: "Data Analytics",
|
||||||
{
|
},
|
||||||
to: "/dashboard/file-browser",
|
{
|
||||||
icon: "mdi-folder",
|
to: "/dashboard/file-browser",
|
||||||
title: "File Browser",
|
icon: "mdi-folder",
|
||||||
},
|
title: "File Browser",
|
||||||
];
|
},
|
||||||
|
];
|
||||||
|
|
||||||
// Add admin menu items if user is admin
|
// Add admin menu items if user is admin
|
||||||
if (userIsAdmin) {
|
if (userIsAdmin) {
|
||||||
console.log('[Dashboard] Adding admin console to default menu');
|
console.log('[Dashboard] Adding admin console to default menu');
|
||||||
baseMenu.push({
|
baseMenu.push({
|
||||||
to: "/dashboard/admin",
|
to: "/dashboard/admin",
|
||||||
icon: "mdi-shield-crown",
|
icon: "mdi-shield-crown",
|
||||||
title: "Admin Console",
|
title: "Admin Console",
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseMenu;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Dashboard] Error computing default menu:', error);
|
||||||
|
// Return basic menu without admin items on error
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
to: "/dashboard/site",
|
||||||
|
icon: "mdi-view-dashboard",
|
||||||
|
title: "Site Analytics",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: "/dashboard/data",
|
||||||
|
icon: "mdi-finance",
|
||||||
|
title: "Data Analytics",
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return baseMenu;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const menu = computed(() =>
|
const menu = computed(() =>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
import { requireAdmin } from '~/server/utils/auth';
|
||||||
|
import { getNocoDbConfiguration, updateInterest, deleteInterest } from '~/server/utils/nocodb';
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
console.log('[ADMIN] Merge duplicates request');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Require admin authentication
|
||||||
|
await requireAdmin(event);
|
||||||
|
|
||||||
|
const body = await readBody(event);
|
||||||
|
const { masterId, duplicateIds, mergeData } = body;
|
||||||
|
|
||||||
|
if (!masterId || !duplicateIds || !Array.isArray(duplicateIds)) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 400,
|
||||||
|
statusMessage: 'Invalid merge request: masterId and duplicateIds array required'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[ADMIN] Merging duplicates:', {
|
||||||
|
masterId,
|
||||||
|
duplicateIds,
|
||||||
|
fieldsToMerge: Object.keys(mergeData || {})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the master record with merged data
|
||||||
|
if (mergeData && Object.keys(mergeData).length > 0) {
|
||||||
|
console.log('[ADMIN] Updating master record with merged data');
|
||||||
|
await updateInterest(masterId.toString(), mergeData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the duplicate records
|
||||||
|
const deletionResults = [];
|
||||||
|
for (const duplicateId of duplicateIds) {
|
||||||
|
try {
|
||||||
|
console.log('[ADMIN] Deleting duplicate record:', duplicateId);
|
||||||
|
await deleteInterest(duplicateId.toString());
|
||||||
|
deletionResults.push({ id: duplicateId, success: true });
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('[ADMIN] Failed to delete duplicate:', duplicateId, error);
|
||||||
|
deletionResults.push({
|
||||||
|
id: duplicateId,
|
||||||
|
success: false,
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[ADMIN] Merge operation completed');
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
masterId,
|
||||||
|
mergedCount: duplicateIds.length,
|
||||||
|
deletionResults,
|
||||||
|
updatedFields: Object.keys(mergeData || {})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('[ADMIN] Failed to merge duplicates:', error);
|
||||||
|
|
||||||
|
if (error.statusCode === 403) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: 'Insufficient permissions. Admin access required.'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.statusCode === 400) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error.statusMessage
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: 'Failed to merge duplicate records'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue