mirror of
https://github.com/agregarr/agregarr.git
synced 2026-05-08 03:41:02 -05:00
fix: custom title not propogating to UI on save, fix linked editing
fixes #29, fixes #37
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
name: Agregarr Latest
|
||||
name: Agregarr Release
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -6,12 +6,18 @@ on:
|
||||
- latest
|
||||
|
||||
jobs:
|
||||
build_and_push:
|
||||
name: Build & Publish Docker Images
|
||||
semantic-release:
|
||||
name: Tag and release latest version
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
@@ -21,12 +27,19 @@ jobs:
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: |
|
||||
agregarr/agregarr:latest
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Install dependencies
|
||||
env:
|
||||
HUSKY: 0
|
||||
run: yarn
|
||||
- name: Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_TOKEN }}
|
||||
run: npx semantic-release
|
||||
|
||||
+1
-1
@@ -229,7 +229,7 @@
|
||||
]
|
||||
],
|
||||
"branches": [
|
||||
"master"
|
||||
"latest"
|
||||
],
|
||||
"npmPublish": false,
|
||||
"publish": [
|
||||
|
||||
@@ -109,6 +109,7 @@ export class DefaultHubConfigService {
|
||||
/**
|
||||
* Update settings for an individual default hub configuration
|
||||
* Preserves computed fields while allowing user changes
|
||||
* If the hub is linked, updates all linked hub instances
|
||||
*/
|
||||
public updateSettings(
|
||||
id: string,
|
||||
@@ -123,32 +124,65 @@ export class DefaultHubConfigService {
|
||||
|
||||
const existingConfig = configs[existingConfigIndex];
|
||||
|
||||
// Merge settings while preserving computed fields
|
||||
const updatedConfig: PlexHubConfig = {
|
||||
...existingConfig, // Preserve all existing fields including computed ones
|
||||
...settings, // Apply user changes
|
||||
// Ensure computed fields stay computed:
|
||||
id: existingConfig.id, // ID never changes
|
||||
isActive: existingConfig.isActive, // isActive is computed elsewhere
|
||||
collectionType: existingConfig.collectionType, // Computed field
|
||||
// Business logic fields can be changed by user:
|
||||
isLinked: settings.isLinked ?? existingConfig.isLinked,
|
||||
linkId: settings.linkId ?? existingConfig.linkId,
|
||||
};
|
||||
// Check if this is a linked hub - if so, update all linked configs
|
||||
const configsToUpdate = [];
|
||||
if (existingConfig.isLinked && existingConfig.linkId) {
|
||||
// Find all configs with the same linkId
|
||||
const linkedConfigs = configs.filter(
|
||||
(c) => c.linkId === existingConfig.linkId && c.isLinked
|
||||
);
|
||||
configsToUpdate.push(...linkedConfigs);
|
||||
logger.info(`Updating ${linkedConfigs.length} linked hub configs`, {
|
||||
label: 'Default Hub Config Service',
|
||||
linkId: existingConfig.linkId,
|
||||
configIds: linkedConfigs.map((c) => c.id),
|
||||
});
|
||||
} else {
|
||||
configsToUpdate.push(existingConfig);
|
||||
}
|
||||
|
||||
// Update the config in place
|
||||
configs[existingConfigIndex] = updatedConfig;
|
||||
const updatedConfigs: PlexHubConfig[] = [];
|
||||
|
||||
// Process each config (could be just one, or multiple if linked)
|
||||
for (const configToUpdate of configsToUpdate) {
|
||||
const configIndex = configs.findIndex((c) => c.id === configToUpdate.id);
|
||||
|
||||
// Merge settings while preserving computed fields and library-specific fields
|
||||
const updatedConfig: PlexHubConfig = {
|
||||
...configToUpdate, // Preserve all existing fields including computed ones
|
||||
...settings, // Apply user changes
|
||||
// Ensure computed fields stay computed:
|
||||
id: configToUpdate.id, // ID never changes
|
||||
isActive: configToUpdate.isActive, // isActive is computed elsewhere
|
||||
collectionType: configToUpdate.collectionType, // Computed field
|
||||
// For linked collections, preserve library-specific fields
|
||||
libraryId: configToUpdate.libraryId, // Don't change the library assignment
|
||||
libraryName: configToUpdate.libraryName, // Don't change the library name
|
||||
hubIdentifier: configToUpdate.hubIdentifier, // Don't change the hub identifier
|
||||
mediaType: configToUpdate.mediaType, // Don't change the media type
|
||||
// Business logic fields can be changed by user:
|
||||
isLinked: settings.isLinked ?? configToUpdate.isLinked,
|
||||
linkId: settings.linkId ?? configToUpdate.linkId,
|
||||
};
|
||||
|
||||
// Update the config in place
|
||||
configs[configIndex] = updatedConfig;
|
||||
updatedConfigs.push(updatedConfig);
|
||||
}
|
||||
|
||||
// Save the updated configs
|
||||
this.saveExistingConfigs(configs);
|
||||
|
||||
logger.info('Individual default hub config updated successfully', {
|
||||
logger.info('Hub config(s) updated successfully', {
|
||||
label: 'Default Hub Config Service',
|
||||
configId: id,
|
||||
configName: updatedConfig.name,
|
||||
updatedCount: updatedConfigs.length,
|
||||
configIds: updatedConfigs.map((c) => c.id),
|
||||
configNames: updatedConfigs.map((c) => c.name),
|
||||
isLinked: existingConfig.isLinked,
|
||||
linkId: existingConfig.linkId || 'none',
|
||||
});
|
||||
|
||||
return updatedConfig;
|
||||
return updatedConfigs[0]; // Return the primary config (the one that was edited)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -133,6 +133,7 @@ export class PreExistingCollectionConfigService {
|
||||
/**
|
||||
* Update settings for an individual pre-existing collection configuration
|
||||
* Preserves computed fields while allowing user changes
|
||||
* If the collection is linked, updates all linked collection instances
|
||||
*/
|
||||
public updateSettings(
|
||||
id: string,
|
||||
@@ -147,36 +148,69 @@ export class PreExistingCollectionConfigService {
|
||||
|
||||
const existingConfig = configs[existingConfigIndex];
|
||||
|
||||
// Merge settings while preserving computed fields
|
||||
const updatedConfig: PreExistingCollectionConfig = {
|
||||
...existingConfig, // Preserve all existing fields including computed ones
|
||||
...settings, // Apply user changes
|
||||
// Ensure computed fields stay computed:
|
||||
id: existingConfig.id, // ID never changes
|
||||
isActive: existingConfig.isActive, // isActive is computed elsewhere
|
||||
collectionType: existingConfig.collectionType, // Computed field
|
||||
// Business logic fields can be changed by user:
|
||||
isLinked: settings.isLinked ?? existingConfig.isLinked,
|
||||
linkId: settings.linkId ?? existingConfig.linkId,
|
||||
isUnlinked: settings.isUnlinked ?? existingConfig.isUnlinked,
|
||||
};
|
||||
// Check if this is a linked collection - if so, update all linked configs
|
||||
const configsToUpdate = [];
|
||||
if (existingConfig.isLinked && existingConfig.linkId) {
|
||||
// Find all configs with the same linkId
|
||||
const linkedConfigs = configs.filter(
|
||||
(c) => c.linkId === existingConfig.linkId && c.isLinked
|
||||
);
|
||||
configsToUpdate.push(...linkedConfigs);
|
||||
logger.info(
|
||||
`Updating ${linkedConfigs.length} linked pre-existing collection configs`,
|
||||
{
|
||||
label: 'Pre-existing Collection Config Service',
|
||||
linkId: existingConfig.linkId,
|
||||
configIds: linkedConfigs.map((c) => c.id),
|
||||
}
|
||||
);
|
||||
} else {
|
||||
configsToUpdate.push(existingConfig);
|
||||
}
|
||||
|
||||
// Update the config in place
|
||||
configs[existingConfigIndex] = updatedConfig;
|
||||
const updatedConfigs: PreExistingCollectionConfig[] = [];
|
||||
|
||||
// Process each config (could be just one, or multiple if linked)
|
||||
for (const configToUpdate of configsToUpdate) {
|
||||
const configIndex = configs.findIndex((c) => c.id === configToUpdate.id);
|
||||
|
||||
// Merge settings while preserving computed fields and library-specific fields
|
||||
const updatedConfig: PreExistingCollectionConfig = {
|
||||
...configToUpdate, // Preserve all existing fields including computed ones
|
||||
...settings, // Apply user changes
|
||||
// Ensure computed fields stay computed:
|
||||
id: configToUpdate.id, // ID never changes
|
||||
isActive: configToUpdate.isActive, // isActive is computed elsewhere
|
||||
collectionType: configToUpdate.collectionType, // Computed field
|
||||
// For linked collections, preserve library-specific fields
|
||||
libraryId: configToUpdate.libraryId, // Don't change the library assignment
|
||||
libraryName: configToUpdate.libraryName, // Don't change the library name
|
||||
collectionRatingKey: configToUpdate.collectionRatingKey, // Don't change the Plex rating key
|
||||
mediaType: configToUpdate.mediaType, // Don't change the media type
|
||||
// Business logic fields can be changed by user:
|
||||
isLinked: settings.isLinked ?? configToUpdate.isLinked,
|
||||
linkId: settings.linkId ?? configToUpdate.linkId,
|
||||
isUnlinked: settings.isUnlinked ?? configToUpdate.isUnlinked,
|
||||
};
|
||||
|
||||
// Update the config in place
|
||||
configs[configIndex] = updatedConfig;
|
||||
updatedConfigs.push(updatedConfig);
|
||||
}
|
||||
|
||||
// Save the updated configs
|
||||
this.saveExistingConfigs(configs);
|
||||
|
||||
logger.info(
|
||||
'Individual pre-existing collection config updated successfully',
|
||||
{
|
||||
label: 'Pre-existing Collection Config Service',
|
||||
configId: id,
|
||||
configName: updatedConfig.name,
|
||||
}
|
||||
);
|
||||
logger.info('Pre-existing collection config(s) updated successfully', {
|
||||
label: 'Pre-existing Collection Config Service',
|
||||
updatedCount: updatedConfigs.length,
|
||||
configIds: updatedConfigs.map((c) => c.id),
|
||||
configNames: updatedConfigs.map((c) => c.name),
|
||||
isLinked: existingConfig.isLinked,
|
||||
linkId: existingConfig.linkId || 'none',
|
||||
});
|
||||
|
||||
return updatedConfig;
|
||||
return updatedConfigs[0]; // Return the primary config (the one that was edited)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+129
-81
@@ -171,104 +171,152 @@ collectionsRoutes.put('/:id/settings', isAuthenticated(), async (req, res) => {
|
||||
|
||||
const existingConfig = configs[existingConfigIndex];
|
||||
|
||||
// Process template to generate actual collection name (same logic as create endpoint)
|
||||
// Get library to determine media type
|
||||
const libraries = settings.plex.libraries || [];
|
||||
const library = libraries.find(
|
||||
(lib) => lib.key === (req.body.libraryId || existingConfig.libraryId)
|
||||
);
|
||||
const libraryMediaType: 'movie' | 'tv' =
|
||||
library && library.type === 'show' ? 'tv' : 'movie';
|
||||
|
||||
const context = {
|
||||
...templateEngine.getDefaultContext(),
|
||||
mediaType: libraryMediaType,
|
||||
days: req.body.customDays || existingConfig.customDays,
|
||||
customdays: req.body.customDays || existingConfig.customDays,
|
||||
statType: req.body.tautulliStatType || existingConfig.tautulliStatType,
|
||||
subtype: req.body.subtype || existingConfig.subtype,
|
||||
};
|
||||
let processedName = templateEngine.processTemplate(
|
||||
req.body.template ||
|
||||
req.body.name ||
|
||||
existingConfig.template ||
|
||||
existingConfig.name ||
|
||||
'',
|
||||
context
|
||||
);
|
||||
|
||||
// For Overseerr user collections, keep {username} and {nickname} as literals
|
||||
if (
|
||||
(req.body.type || existingConfig.type) === 'overseerr' &&
|
||||
(req.body.subtype || existingConfig.subtype) === 'users'
|
||||
) {
|
||||
const defaultContext = templateEngine.getDefaultContext();
|
||||
if (defaultContext.username) {
|
||||
processedName = processedName.replace(
|
||||
new RegExp(defaultContext.username, 'g'),
|
||||
'{username}'
|
||||
);
|
||||
}
|
||||
if (defaultContext.nickname) {
|
||||
processedName = processedName.replace(
|
||||
new RegExp(defaultContext.nickname, 'g'),
|
||||
'{nickname}'
|
||||
);
|
||||
}
|
||||
// Check if this is a linked collection - if so, update all linked configs
|
||||
const configsToUpdate = [];
|
||||
if (existingConfig.isLinked && existingConfig.linkId) {
|
||||
// Find all configs with the same linkId
|
||||
const linkedConfigs = configs.filter(
|
||||
(c) => c.linkId === existingConfig.linkId && c.isLinked
|
||||
);
|
||||
configsToUpdate.push(...linkedConfigs);
|
||||
logger.info(
|
||||
`Updating ${linkedConfigs.length} linked collection configs`,
|
||||
{
|
||||
label: 'Collections API',
|
||||
linkId: existingConfig.linkId,
|
||||
configIds: linkedConfigs.map((c) => c.id),
|
||||
}
|
||||
);
|
||||
} else {
|
||||
configsToUpdate.push(existingConfig);
|
||||
}
|
||||
|
||||
// Merge settings while preserving computed fields
|
||||
const updatedConfig = {
|
||||
...existingConfig, // Preserve all existing fields including computed ones
|
||||
...req.body, // Apply user changes
|
||||
name: processedName, // Use processed template name
|
||||
// Ensure computed fields stay computed:
|
||||
id: existingConfig.id, // ID never changes
|
||||
isActive: existingConfig.isActive, // Preserve sync service's isActive calculation
|
||||
};
|
||||
// Get libraries for template processing
|
||||
const libraries = settings.plex.libraries || [];
|
||||
const updatedConfigs: CollectionConfig[] = [];
|
||||
const affectedLibraryIds: string[] = [];
|
||||
|
||||
// Process each config (could be just one, or multiple if linked)
|
||||
for (const configToUpdate of configsToUpdate) {
|
||||
const configIndex = configs.findIndex((c) => c.id === configToUpdate.id);
|
||||
|
||||
// Get library to determine media type for template processing
|
||||
const library = libraries.find(
|
||||
(lib) => lib.key === (req.body.libraryId || configToUpdate.libraryId)
|
||||
);
|
||||
const libraryMediaType: 'movie' | 'tv' =
|
||||
library && library.type === 'show' ? 'tv' : 'movie';
|
||||
|
||||
const context = {
|
||||
...templateEngine.getDefaultContext(),
|
||||
mediaType: libraryMediaType,
|
||||
days: req.body.customDays || configToUpdate.customDays,
|
||||
customdays: req.body.customDays || configToUpdate.customDays,
|
||||
statType: req.body.tautulliStatType || configToUpdate.tautulliStatType,
|
||||
subtype: req.body.subtype || configToUpdate.subtype,
|
||||
};
|
||||
|
||||
let processedName = templateEngine.processTemplate(
|
||||
req.body.template ||
|
||||
req.body.name ||
|
||||
configToUpdate.template ||
|
||||
configToUpdate.name ||
|
||||
'',
|
||||
context
|
||||
);
|
||||
|
||||
// For Overseerr user collections, keep {username} and {nickname} as literals
|
||||
if (
|
||||
(req.body.type || configToUpdate.type) === 'overseerr' &&
|
||||
(req.body.subtype || configToUpdate.subtype) === 'users'
|
||||
) {
|
||||
const defaultContext = templateEngine.getDefaultContext();
|
||||
if (defaultContext.username) {
|
||||
processedName = processedName.replace(
|
||||
new RegExp(defaultContext.username, 'g'),
|
||||
'{username}'
|
||||
);
|
||||
}
|
||||
if (defaultContext.nickname) {
|
||||
processedName = processedName.replace(
|
||||
new RegExp(defaultContext.nickname, 'g'),
|
||||
'{nickname}'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge settings while preserving computed fields and library-specific fields
|
||||
const updatedConfig: CollectionConfig = {
|
||||
...configToUpdate, // Preserve all existing fields including computed ones
|
||||
...req.body, // Apply user changes
|
||||
name: processedName, // Use processed template name
|
||||
// Ensure computed fields stay computed:
|
||||
id: configToUpdate.id, // ID never changes
|
||||
isActive: configToUpdate.isActive, // Preserve sync service's isActive calculation
|
||||
// For linked collections, preserve library-specific fields
|
||||
libraryId: configToUpdate.libraryId, // Don't change the library assignment
|
||||
libraryName: configToUpdate.libraryName, // Don't change the library name
|
||||
};
|
||||
|
||||
// Update the config in place
|
||||
configs[configIndex] = updatedConfig;
|
||||
updatedConfigs.push(updatedConfig);
|
||||
|
||||
// Track affected libraries for auto-reorder
|
||||
const libraryId = Array.isArray(updatedConfig.libraryId)
|
||||
? updatedConfig.libraryId[0]
|
||||
: updatedConfig.libraryId;
|
||||
if (libraryId && !affectedLibraryIds.includes(libraryId)) {
|
||||
affectedLibraryIds.push(libraryId);
|
||||
}
|
||||
|
||||
// Mark collection as needing sync due to modification
|
||||
settings.markCollectionModified(configToUpdate.id, 'collection');
|
||||
}
|
||||
|
||||
// Update the config in place
|
||||
configs[existingConfigIndex] = updatedConfig;
|
||||
settings.plex.collectionConfigs = configs;
|
||||
settings.save();
|
||||
|
||||
// Mark collection as needing sync due to modification
|
||||
settings.markCollectionModified(id, 'collection');
|
||||
|
||||
logger.info('Individual collection config updated successfully', {
|
||||
logger.info('Collection config(s) updated successfully', {
|
||||
label: 'Collections API',
|
||||
configId: id,
|
||||
configName: updatedConfig.name,
|
||||
updatedCount: updatedConfigs.length,
|
||||
configIds: updatedConfigs.map((c) => c.id),
|
||||
configNames: updatedConfigs.map((c) => c.name),
|
||||
isLinked: existingConfig.isLinked,
|
||||
linkId: existingConfig.linkId || 'none',
|
||||
});
|
||||
|
||||
// Auto-reorder after visibility changes to assign proper sort orders
|
||||
const { autoReorderLibrary } = await import('@server/routes/reorder');
|
||||
try {
|
||||
const libraryId = Array.isArray(updatedConfig.libraryId)
|
||||
? updatedConfig.libraryId[0]
|
||||
: updatedConfig.libraryId;
|
||||
await autoReorderLibrary(libraryId, 'home');
|
||||
await autoReorderLibrary(libraryId, 'library');
|
||||
logger.debug(
|
||||
`Auto-reordering completed after collection settings update for library ${libraryId}`,
|
||||
{
|
||||
for (const libraryId of affectedLibraryIds) {
|
||||
try {
|
||||
await autoReorderLibrary(libraryId, 'home');
|
||||
await autoReorderLibrary(libraryId, 'library');
|
||||
logger.debug(
|
||||
`Auto-reordering completed after collection settings update for library ${libraryId}`,
|
||||
{
|
||||
label: 'Collections API - Auto Reorder',
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
logger.warn('Failed to auto-reorder after collection settings update', {
|
||||
label: 'Collections API - Auto Reorder',
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
logger.warn('Failed to auto-reorder after collection settings update', {
|
||||
label: 'Collections API - Auto Reorder',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
// Don't fail the settings update if reordering fails
|
||||
libraryId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
// Don't fail the settings update if reordering fails
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
collectionConfig: updatedConfig,
|
||||
message: 'Collection settings updated successfully',
|
||||
collectionConfig: updatedConfigs[0], // Return the primary config (the one that was edited)
|
||||
updatedConfigs: updatedConfigs, // Include all updated configs in response
|
||||
message: `${updatedConfigs.length} collection config${
|
||||
updatedConfigs.length === 1 ? '' : 's'
|
||||
} updated successfully`,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Failed to update individual collection settings', {
|
||||
logger.error('Failed to update collection settings', {
|
||||
label: 'Collections API',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
configId: req.params.id,
|
||||
|
||||
@@ -1383,8 +1383,12 @@ const CollectionFormConfigForm = ({
|
||||
libraryId: values.libraryId as string,
|
||||
libraryName: values.libraryName as string,
|
||||
name: generateCollectionName(values as CollectionFormConfig),
|
||||
// For custom templates, pass both custom templates and let backend choose
|
||||
template: values.template,
|
||||
// For custom templates, send the actual custom text as the template
|
||||
template:
|
||||
values.template === 'custom'
|
||||
? (values as CollectionFormConfig).customMovieTemplate ||
|
||||
(values as CollectionFormConfig).customTVTemplate
|
||||
: values.template,
|
||||
customMovieTemplate:
|
||||
values.template === 'custom'
|
||||
? (values as CollectionFormConfig).customMovieTemplate
|
||||
@@ -2074,6 +2078,16 @@ const CollectionFormConfigForm = ({
|
||||
return values.name || 'User Collection';
|
||||
}
|
||||
|
||||
// Handle custom templates - use the actual custom template text, not "custom"
|
||||
if (values.template === 'custom') {
|
||||
// Use the first available custom template (movie or TV)
|
||||
const customTemplate =
|
||||
values.customMovieTemplate || values.customTVTemplate;
|
||||
if (customTemplate) {
|
||||
return customTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the template as the name - backend will process it with proper library context
|
||||
return values.template || values.name || 'Collection';
|
||||
}
|
||||
|
||||
@@ -1436,81 +1436,13 @@ const CollectionSettings = ({
|
||||
let changedConfigs: CollectionFormConfig[] = [];
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
// Update existing config
|
||||
if (
|
||||
collectionConfig.isLinked &&
|
||||
collectionConfig.linkId &&
|
||||
collectionConfig.libraryIds
|
||||
) {
|
||||
// This is a linked collection update - update all linked configs with same type/subtype
|
||||
updatedConfigs = [...localCollectionConfigs];
|
||||
// Update existing config - backend will handle linked collection propagation
|
||||
updatedConfigs = [...localCollectionConfigs];
|
||||
updatedConfigs[existingIndex] = collectionConfig;
|
||||
|
||||
// Find all linked configs with same type/subtype and linkId
|
||||
const linkedConfigs = updatedConfigs.filter(
|
||||
(c) =>
|
||||
c.type === collectionConfig.type &&
|
||||
c.subtype === collectionConfig.subtype &&
|
||||
c.linkId === collectionConfig.linkId &&
|
||||
c.isLinked // Only linked configs
|
||||
);
|
||||
|
||||
// Update all linked configs with new settings (except library-specific fields)
|
||||
linkedConfigs.forEach((linkedConfig) => {
|
||||
const index = updatedConfigs.findIndex(
|
||||
(c) => c.id === linkedConfig.id
|
||||
);
|
||||
if (index >= 0) {
|
||||
updatedConfigs[index] = {
|
||||
...linkedConfig,
|
||||
// Update shared settings
|
||||
template: collectionConfig.template,
|
||||
customMovieTemplate: collectionConfig.customMovieTemplate,
|
||||
customTVTemplate: collectionConfig.customTVTemplate,
|
||||
visibilityConfig: collectionConfig.visibilityConfig,
|
||||
maxItems: collectionConfig.maxItems,
|
||||
mediaType: collectionConfig.mediaType,
|
||||
customDays: collectionConfig.customDays,
|
||||
tautulliStatType: collectionConfig.tautulliStatType,
|
||||
downloadMode: collectionConfig.downloadMode,
|
||||
searchMissingMovies: collectionConfig.searchMissingMovies,
|
||||
searchMissingTV: collectionConfig.searchMissingTV,
|
||||
autoApproveMovies: collectionConfig.autoApproveMovies,
|
||||
autoApproveTV: collectionConfig.autoApproveTV,
|
||||
maxSeasonsToRequest: collectionConfig.maxSeasonsToRequest,
|
||||
maxPositionToProcess: collectionConfig.maxPositionToProcess,
|
||||
traktCustomListUrl: collectionConfig.traktCustomListUrl,
|
||||
tmdbCustomListUrl: collectionConfig.tmdbCustomListUrl,
|
||||
imdbCustomListUrl: collectionConfig.imdbCustomListUrl,
|
||||
letterboxdCustomListUrl:
|
||||
collectionConfig.letterboxdCustomListUrl,
|
||||
reverseOrder: collectionConfig.reverseOrder,
|
||||
randomizeOrder: collectionConfig.randomizeOrder,
|
||||
timeRestriction: collectionConfig.timeRestriction,
|
||||
customPoster: collectionConfig.customPoster,
|
||||
isUnlinked: collectionConfig.isUnlinked,
|
||||
// Keep library-specific fields unchanged
|
||||
libraryId: linkedConfig.libraryId,
|
||||
libraryName: linkedConfig.libraryName,
|
||||
collectionRatingKey: linkedConfig.collectionRatingKey,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Only send API calls for the linked configs that were actually changed
|
||||
changedConfigs = linkedConfigs.map((linkedConfig) => {
|
||||
const index = updatedConfigs.findIndex(
|
||||
(c) => c.id === linkedConfig.id
|
||||
);
|
||||
return updatedConfigs[index];
|
||||
});
|
||||
} else {
|
||||
// Regular single config update
|
||||
updatedConfigs = [...localCollectionConfigs];
|
||||
updatedConfigs[existingIndex] = collectionConfig;
|
||||
|
||||
// Only send API call for the single config that changed
|
||||
changedConfigs = [collectionConfig];
|
||||
}
|
||||
// Always send API call for only the single config that changed
|
||||
// Backend will automatically propagate changes to linked configs
|
||||
changedConfigs = [collectionConfig];
|
||||
} else {
|
||||
// Add new config(s) - Use new simplified backend API
|
||||
try {
|
||||
@@ -1570,13 +1502,15 @@ const CollectionSettings = ({
|
||||
}
|
||||
}
|
||||
|
||||
// Update local React state with all changes for UI
|
||||
setLocalCollectionConfigs(updatedConfigs);
|
||||
|
||||
// Only send API calls for collections that actually changed
|
||||
if (changedConfigs.length > 0) {
|
||||
await saveCollectionConfigs(changedConfigs);
|
||||
// Refresh data from backend - this will pick up any linked collection changes
|
||||
// that the backend automatically propagated
|
||||
revalidateAll();
|
||||
} else {
|
||||
// Update local React state if no API calls needed
|
||||
setLocalCollectionConfigs(updatedConfigs);
|
||||
}
|
||||
|
||||
setShowConfigForm(false);
|
||||
|
||||
Reference in New Issue
Block a user