fixed assets isolation issue
This commit is contained in:
parent
fcc3d9e868
commit
34770304c5
@ -19,7 +19,7 @@ class AssetsDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get ENUM_FIELDS() {
|
||||
return ['asset_type', 'type', 'is_public'];
|
||||
return ['asset_type', 'type', 'is_public', 'projectId'];
|
||||
}
|
||||
|
||||
static get CSV_FIELDS() {
|
||||
|
||||
@ -265,7 +265,13 @@ class GenericDBApi {
|
||||
let include = [...this.FIND_ALL_INCLUDES];
|
||||
|
||||
if (filter.id) {
|
||||
where.id = Utils.uuid(filter.id);
|
||||
const validId = Utils.uuid(filter.id);
|
||||
if (validId) {
|
||||
where.id = validId;
|
||||
} else {
|
||||
// Invalid UUID provided - return empty results immediately
|
||||
return { rows: [], count: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
for (const field of this.SEARCHABLE_FIELDS) {
|
||||
@ -310,31 +316,37 @@ class GenericDBApi {
|
||||
for (const rel of this.RELATION_FILTERS) {
|
||||
if (filter[rel.filterKey]) {
|
||||
const searchTerms = filter[rel.filterKey].split('|');
|
||||
|
||||
// Filter out null UUIDs - only keep valid ones
|
||||
const validUuids = searchTerms
|
||||
.map((term) => Utils.uuid(term))
|
||||
.filter((id) => id !== null);
|
||||
|
||||
// Build OR conditions array
|
||||
const orConditions = [];
|
||||
|
||||
// Add UUID condition only if there are valid UUIDs
|
||||
if (validUuids.length > 0) {
|
||||
orConditions.push({ id: { [Op.in]: validUuids } });
|
||||
}
|
||||
|
||||
// Add text search condition if searchField is defined
|
||||
if (rel.searchField) {
|
||||
orConditions.push({
|
||||
[rel.searchField]: {
|
||||
[Op.or]: searchTerms.map((term) => ({
|
||||
[Op.iLike]: `%${term}%`,
|
||||
})),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const relInclude = {
|
||||
model: rel.model,
|
||||
as: rel.as,
|
||||
required: searchTerms.length > 0,
|
||||
required: orConditions.length > 0,
|
||||
where:
|
||||
searchTerms.length > 0
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: searchTerms.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
rel.searchField
|
||||
? {
|
||||
[rel.searchField]: {
|
||||
[Op.or]: searchTerms.map((term) => ({
|
||||
[Op.iLike]: `%${term}%`,
|
||||
})),
|
||||
},
|
||||
}
|
||||
: {},
|
||||
],
|
||||
}
|
||||
: undefined,
|
||||
orConditions.length > 0 ? { [Op.or]: orConditions } : undefined,
|
||||
};
|
||||
include = [relInclude, ...include];
|
||||
}
|
||||
|
||||
@ -1,16 +1,17 @@
|
||||
const validator = require('validator');
|
||||
const { v4: uuid } = require('uuid');
|
||||
const Sequelize = require('./models').Sequelize;
|
||||
|
||||
module.exports = class Utils {
|
||||
/**
|
||||
* Validates a UUID string.
|
||||
* @param {*} value - The value to validate as UUID
|
||||
* @returns {string|null} - The valid UUID string, or null if invalid
|
||||
*/
|
||||
static uuid(value) {
|
||||
let id = value;
|
||||
|
||||
if (!validator.isUUID(id)) {
|
||||
id = uuid();
|
||||
if (value && validator.isUUID(String(value))) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return id;
|
||||
return null;
|
||||
}
|
||||
|
||||
static ilike(model, column, value) {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { logger } from '../../lib/logger';
|
||||
|
||||
export type Project = {
|
||||
@ -32,81 +31,47 @@ export function useProjectSelector({
|
||||
}, [router.query.projectId]);
|
||||
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
const [selectedProjectId, setSelectedProjectId] = useState('');
|
||||
const [isLoadingProjects, setIsLoadingProjects] = useState(false);
|
||||
|
||||
// Redirect if no projectId in URL
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return;
|
||||
if (!routeProjectId) {
|
||||
router.replace('/projects/projects-list');
|
||||
}
|
||||
}, [router.isReady, routeProjectId, router]);
|
||||
|
||||
const loadProjects = useCallback(async () => {
|
||||
setIsLoadingProjects(true);
|
||||
|
||||
try {
|
||||
let rows: Project[] = [];
|
||||
const response = await axios.get(
|
||||
'/projects?limit=100&page=0&sort=desc&field=updatedAt',
|
||||
);
|
||||
rows = Array.isArray(response?.data?.rows) ? response.data.rows : [];
|
||||
|
||||
if (rows.length === 0) {
|
||||
const autocompleteResponse = await axios.get(
|
||||
'/projects/autocomplete?limit=100',
|
||||
);
|
||||
const autocompleteItems = Array.isArray(autocompleteResponse?.data)
|
||||
? autocompleteResponse.data
|
||||
: [];
|
||||
rows = autocompleteItems.map((item: { id: string; label: string }) => ({
|
||||
id: item.id,
|
||||
name: item.label,
|
||||
}));
|
||||
}
|
||||
|
||||
if (rows.length === 0) {
|
||||
toast('Please create a project first', {
|
||||
type: 'info',
|
||||
position: 'bottom-center',
|
||||
});
|
||||
router.replace('/projects/projects-new');
|
||||
return;
|
||||
}
|
||||
|
||||
const rows = Array.isArray(response?.data?.rows) ? response.data.rows : [];
|
||||
setProjects(rows);
|
||||
|
||||
if (
|
||||
routeProjectId &&
|
||||
rows.some((project) => project.id === routeProjectId)
|
||||
) {
|
||||
setSelectedProjectId(routeProjectId);
|
||||
} else {
|
||||
setSelectedProjectId((prev) => {
|
||||
if (rows.some((project) => project.id === prev)) return prev;
|
||||
return rows[0]?.id || '';
|
||||
});
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
logger.error('Failed to load projects:', { error: errorMessage });
|
||||
setProjects([]);
|
||||
setSelectedProjectId('');
|
||||
toast('Failed to load projects', {
|
||||
type: 'error',
|
||||
position: 'bottom-center',
|
||||
});
|
||||
} finally {
|
||||
setIsLoadingProjects(false);
|
||||
}
|
||||
}, [routeProjectId, router]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentUser) return;
|
||||
loadProjects();
|
||||
}, [routeProjectId, currentUser, loadProjects]);
|
||||
}, [currentUser, loadProjects]);
|
||||
|
||||
const selectedProjectName = useMemo(() => {
|
||||
return projects.find((p) => p.id === selectedProjectId)?.name || '';
|
||||
}, [projects, selectedProjectId]);
|
||||
return projects.find((p) => p.id === routeProjectId)?.name || '';
|
||||
}, [projects, routeProjectId]);
|
||||
|
||||
return {
|
||||
projects,
|
||||
selectedProjectId,
|
||||
selectedProjectId: routeProjectId, // Direct from URL - no fallback
|
||||
isLoadingProjects,
|
||||
selectedProjectName,
|
||||
};
|
||||
|
||||
@ -5,6 +5,7 @@ import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { toast, ToastContainer } from 'react-toastify';
|
||||
@ -17,6 +18,7 @@ import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import {
|
||||
fetch as fetchAssets,
|
||||
deleteItem as deleteAsset,
|
||||
clearState as clearAssets,
|
||||
} from '../../stores/assets/assetsSlice';
|
||||
import AssetSectionCard, {
|
||||
Asset,
|
||||
@ -88,12 +90,14 @@ const AssetsTablesPage = () => {
|
||||
const { selectedProjectId, isLoadingProjects, selectedProjectName } =
|
||||
useProjectSelector({ currentUser });
|
||||
|
||||
const prevProjectIdRef = useRef<string>('');
|
||||
|
||||
const loadAssets = useCallback(
|
||||
(projectId: string) => {
|
||||
if (!projectId) return;
|
||||
dispatch(
|
||||
fetchAssets({
|
||||
query: `?limit=500&page=0&sort=desc&field=createdAt&project=${projectId}`,
|
||||
query: `?limit=500&page=0&sort=desc&field=createdAt&projectId=${projectId}`,
|
||||
}),
|
||||
);
|
||||
},
|
||||
@ -106,8 +110,16 @@ const AssetsTablesPage = () => {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedProjectId) return;
|
||||
|
||||
// Clear on project change
|
||||
if (prevProjectIdRef.current !== selectedProjectId) {
|
||||
dispatch(clearAssets());
|
||||
}
|
||||
prevProjectIdRef.current = selectedProjectId;
|
||||
|
||||
loadAssets(selectedProjectId);
|
||||
}, [selectedProjectId, loadAssets]);
|
||||
}, [selectedProjectId, dispatch, loadAssets]);
|
||||
|
||||
const hasCreatePermission = Boolean(
|
||||
currentUser && hasPermission(currentUser, 'CREATE_ASSETS'),
|
||||
|
||||
@ -625,7 +625,7 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
|
||||
`/tour_pages?limit=500&sort=asc&field=sort_order&project=${projectId}&environment=dev`,
|
||||
),
|
||||
axios.get(
|
||||
`/assets?limit=500&page=0&sort=desc&field=createdAt&project=${projectId}`,
|
||||
`/assets?limit=500&page=0&sort=desc&field=createdAt&projectId=${projectId}`,
|
||||
),
|
||||
axios.get(
|
||||
`/project-element-defaults?projectId=${projectId}&limit=200&page=0&sort=asc&field=sort_order`,
|
||||
|
||||
@ -157,7 +157,7 @@ const ProjectElementDefaultDetailsPage = () => {
|
||||
if (nextItem.projectId) {
|
||||
try {
|
||||
const assetsResponse = await axios.get(
|
||||
`/assets?limit=500&page=0&sort=desc&field=createdAt&project=${nextItem.projectId}`,
|
||||
`/assets?limit=500&page=0&sort=desc&field=createdAt&projectId=${nextItem.projectId}`,
|
||||
);
|
||||
const assetRows: ConstructorAsset[] = Array.isArray(
|
||||
assetsResponse?.data?.rows,
|
||||
|
||||
@ -69,7 +69,7 @@ const EditProjectsPage = () => {
|
||||
setIsLoadingLogoAssets(true);
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`/assets?limit=500&page=0&sort=desc&field=createdAt&project=${projectId}`,
|
||||
`/assets?limit=500&page=0&sort=desc&field=createdAt&projectId=${projectId}`,
|
||||
);
|
||||
const rows = Array.isArray(response?.data?.rows)
|
||||
? response.data.rows
|
||||
@ -103,11 +103,14 @@ const EditProjectsPage = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setLogoAssets([]);
|
||||
setIsLoadingLogoAssets(true);
|
||||
|
||||
if (typeof id === 'string' && id) {
|
||||
void loadLogoAssets(id);
|
||||
return;
|
||||
} else {
|
||||
setIsLoadingLogoAssets(false);
|
||||
}
|
||||
setLogoAssets([]);
|
||||
}, [id]);
|
||||
|
||||
// Sync form values with fetched data (consolidated from redundant useEffects)
|
||||
|
||||
@ -19,6 +19,7 @@ export const {
|
||||
deleteItemsByIds,
|
||||
uploadCsv,
|
||||
setRefetch,
|
||||
clearState,
|
||||
} = actions;
|
||||
export const assetsSlice = slice;
|
||||
|
||||
|
||||
@ -227,6 +227,10 @@ export function createEntitySlice<T extends BaseEntity>(
|
||||
setRefetch: (state, action: PayloadAction<boolean>) => {
|
||||
state.refetch = action.payload;
|
||||
},
|
||||
clearState: (state) => {
|
||||
(state as Record<string, unknown>)[name] = [];
|
||||
state.count = 0;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
// Fetch handlers
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user