@@ -4,6 +4,269 @@ import { readToken } from '@app/services/localStorage.service';
44import { useHandleLogout } from './authUtils' ;
55import { SettingsGroupName , SettingsGroupType } from '@app/types/settings.types' ;
66
7+ // Helper function to extract the correct nested data for each settings group
8+ const extractSettingsForGroup = ( settings : any , groupName : string ) => {
9+ console . log ( `Extracting settings for group: ${ groupName } ` , settings ) ;
10+
11+ let rawData : any = { } ;
12+
13+ switch ( groupName ) {
14+ case 'image_moderation' :
15+ rawData = settings ?. content_filtering ?. image_moderation || { } ;
16+ break ;
17+
18+ case 'content_filter' :
19+ rawData = settings ?. content_filtering ?. text_filter || { } ;
20+ break ;
21+
22+ case 'nest_feeder' :
23+ rawData = settings ?. external_services ?. nest_feeder || { } ;
24+ break ;
25+
26+ case 'ollama' :
27+ rawData = settings ?. external_services ?. ollama || { } ;
28+ break ;
29+
30+ case 'wallet' :
31+ rawData = settings ?. external_services ?. wallet || { } ;
32+ break ;
33+
34+ case 'relay_info' :
35+ rawData = settings ?. relay || { } ;
36+ break ;
37+
38+ case 'general' :
39+ rawData = settings ?. server || { } ;
40+ break ;
41+
42+ default :
43+ console . warn ( `Unknown settings group: ${ groupName } ` ) ;
44+ return { } ;
45+ }
46+
47+ // Handle the prefixed field name issue
48+ // The backend returns both prefixed and unprefixed fields, but forms expect prefixed ones
49+ if ( groupName === 'image_moderation' && rawData ) {
50+ const processedData : any = { } ;
51+
52+ // Map unprefixed fields to prefixed ones that the form expects
53+ const imageModerationMappings : Record < string , string [ ] > = {
54+ 'image_moderation_api' : [ 'image_moderation_api' , 'api' ] ,
55+ 'image_moderation_check_interval' : [ 'image_moderation_check_interval_seconds' , 'check_interval_seconds' ] ,
56+ 'image_moderation_concurrency' : [ 'image_moderation_concurrency' , 'concurrency' ] ,
57+ 'image_moderation_enabled' : [ 'image_moderation_enabled' , 'enabled' ] ,
58+ 'image_moderation_mode' : [ 'image_moderation_mode' , 'mode' ] ,
59+ 'image_moderation_temp_dir' : [ 'image_moderation_temp_dir' , 'temp_dir' ] ,
60+ 'image_moderation_threshold' : [ 'image_moderation_threshold' , 'threshold' ] ,
61+ 'image_moderation_timeout' : [ 'image_moderation_timeout_seconds' , 'timeout_seconds' ]
62+ } ;
63+
64+ // Map fields, prioritizing prefixed versions if they exist
65+ Object . entries ( imageModerationMappings ) . forEach ( ( [ formField , possibleBackendFields ] ) => {
66+ for ( const backendField of possibleBackendFields ) {
67+ if ( rawData [ backendField ] !== undefined ) {
68+ processedData [ formField ] = rawData [ backendField ] ;
69+ break ; // Use the first found value
70+ }
71+ }
72+ } ) ;
73+
74+ console . log ( `Processed ${ groupName } data:` , processedData ) ;
75+ return processedData ;
76+ }
77+
78+ if ( groupName === 'content_filter' && rawData ) {
79+ const processedData : any = { } ;
80+
81+ // Handle content filter prefixed fields
82+ const contentFilterMappings : Record < string , string > = {
83+ 'content_filter_cache_size' : 'cache_size' ,
84+ 'content_filter_cache_ttl' : 'cache_ttl_seconds' ,
85+ 'content_filter_enabled' : 'enabled' ,
86+ 'full_text_kinds' : 'full_text_search_kinds' // Special mapping
87+ } ;
88+
89+ // Start with raw data
90+ Object . keys ( rawData ) . forEach ( key => {
91+ processedData [ key ] = rawData [ key ] ;
92+ } ) ;
93+
94+ // Apply prefixed field mappings
95+ Object . entries ( contentFilterMappings ) . forEach ( ( [ prefixedKey , rawKey ] ) => {
96+ if ( rawData [ rawKey ] !== undefined ) {
97+ processedData [ prefixedKey ] = rawData [ rawKey ] ;
98+ }
99+ } ) ;
100+
101+ console . log ( `Processed ${ groupName } data:` , processedData ) ;
102+ return processedData ;
103+ }
104+
105+ // Add more mappings for other services that might need prefixed fields
106+ if ( groupName === 'nest_feeder' && rawData ) {
107+ const processedData : any = { } ;
108+
109+ // Handle nest_feeder prefixed fields based on the prefixedSettingsMap
110+ const nestFeederMappings : Record < string , string > = {
111+ 'nest_feeder_cache_size' : 'cache_size' ,
112+ 'nest_feeder_cache_ttl' : 'cache_ttl' ,
113+ 'nest_feeder_enabled' : 'enabled' ,
114+ 'nest_feeder_timeout' : 'timeout' ,
115+ 'nest_feeder_url' : 'url'
116+ } ;
117+
118+ // Start with raw data
119+ Object . keys ( rawData ) . forEach ( key => {
120+ processedData [ key ] = rawData [ key ] ;
121+ } ) ;
122+
123+ // Apply prefixed field mappings
124+ Object . entries ( nestFeederMappings ) . forEach ( ( [ prefixedKey , rawKey ] ) => {
125+ if ( rawData [ rawKey ] !== undefined ) {
126+ processedData [ prefixedKey ] = rawData [ rawKey ] ;
127+ }
128+ } ) ;
129+
130+ console . log ( `Processed ${ groupName } data:` , processedData ) ;
131+ return processedData ;
132+ }
133+
134+ // Handle relay info field name mapping
135+ if ( groupName === 'relay_info' && rawData ) {
136+ const processedData : any = { } ;
137+
138+ // Map backend field names to frontend field names
139+ const relayInfoMappings : Record < string , string > = {
140+ 'relayname' : 'name' ,
141+ 'relaydescription' : 'description' ,
142+ 'relaycontact' : 'contact' ,
143+ 'relaypubkey' : 'pubkey' , // This might not exist in backend, will be empty
144+ 'relaydhtkey' : 'dht_key' ,
145+ 'relaysoftware' : 'software' ,
146+ 'relayversion' : 'version' ,
147+ 'relaysupportednips' : 'supported_nips'
148+ } ;
149+
150+ // Apply field mappings
151+ Object . entries ( relayInfoMappings ) . forEach ( ( [ frontendKey , backendKey ] ) => {
152+ if ( rawData [ backendKey ] !== undefined ) {
153+ processedData [ frontendKey ] = rawData [ backendKey ] ;
154+ } else {
155+ // Set default values for missing fields
156+ if ( frontendKey === 'relaypubkey' ) {
157+ processedData [ frontendKey ] = '' ; // Default empty for pubkey
158+ } else if ( frontendKey === 'relaysupportednips' ) {
159+ processedData [ frontendKey ] = [ ] ; // Default empty array
160+ }
161+ }
162+ } ) ;
163+
164+ console . log ( `Processed ${ groupName } data:` , processedData ) ;
165+ return processedData ;
166+ }
167+
168+ return rawData ;
169+ } ;
170+
171+ // Helper function to build the nested update structure for the new API
172+ const buildNestedUpdate = ( groupName : string , data : any ) => {
173+ switch ( groupName ) {
174+ case 'image_moderation' :
175+ return {
176+ settings : {
177+ content_filtering : {
178+ image_moderation : data
179+ }
180+ }
181+ } ;
182+
183+ case 'content_filter' :
184+ return {
185+ settings : {
186+ content_filtering : {
187+ text_filter : data
188+ }
189+ }
190+ } ;
191+
192+ case 'nest_feeder' :
193+ return {
194+ settings : {
195+ external_services : {
196+ nest_feeder : data
197+ }
198+ }
199+ } ;
200+
201+ case 'ollama' :
202+ return {
203+ settings : {
204+ external_services : {
205+ ollama : data
206+ }
207+ }
208+ } ;
209+
210+ case 'wallet' :
211+ return {
212+ settings : {
213+ external_services : {
214+ wallet : data
215+ }
216+ }
217+ } ;
218+
219+ case 'relay_info' :
220+ // Reverse the field mapping for saving
221+ const backendRelayData : any = { } ;
222+ const relayFieldMappings : Record < string , string > = {
223+ 'name' : 'relayname' ,
224+ 'description' : 'relaydescription' ,
225+ 'contact' : 'relaycontact' ,
226+ 'dht_key' : 'relaydhtkey' ,
227+ 'software' : 'relaysoftware' ,
228+ 'version' : 'relayversion' ,
229+ 'supported_nips' : 'relaysupportednips'
230+ // Note: not mapping pubkey since it doesn't exist in backend
231+ } ;
232+
233+ Object . entries ( relayFieldMappings ) . forEach ( ( [ backendKey , frontendKey ] ) => {
234+ if ( data [ frontendKey ] !== undefined ) {
235+ // Special handling for supported_nips to ensure they're numbers
236+ if ( backendKey === 'supported_nips' ) {
237+ const nips = data [ frontendKey ] ;
238+ if ( Array . isArray ( nips ) ) {
239+ backendRelayData [ backendKey ] = nips . map ( ( nip : any ) => Number ( nip ) ) . filter ( ( nip : number ) => ! isNaN ( nip ) ) ;
240+ } else {
241+ backendRelayData [ backendKey ] = [ ] ;
242+ }
243+ } else {
244+ backendRelayData [ backendKey ] = data [ frontendKey ] ;
245+ }
246+ }
247+ } ) ;
248+
249+ return {
250+ settings : {
251+ relay : backendRelayData
252+ }
253+ } ;
254+
255+ case 'general' :
256+ return {
257+ settings : {
258+ server : data
259+ }
260+ } ;
261+
262+ default :
263+ console . warn ( `Unknown settings group for save: ${ groupName } ` ) ;
264+ return {
265+ settings : { }
266+ } ;
267+ }
268+ } ;
269+
7270interface UseGenericSettingsResult < T > {
8271 settings : T | null ;
9272 loading : boolean ;
@@ -35,7 +298,7 @@ const useGenericSettings = <T extends SettingsGroupName>(
35298
36299 console . log ( `Fetching ${ groupName } settings...` ) ;
37300
38- const response = await fetch ( `${ config . baseURL } /api/settings/ ${ groupName } ` , {
301+ const response = await fetch ( `${ config . baseURL } /api/settings` , {
39302 headers : {
40303 'Authorization' : `Bearer ${ token } ` ,
41304 } ,
@@ -52,10 +315,10 @@ const useGenericSettings = <T extends SettingsGroupName>(
52315 }
53316
54317 const data = await response . json ( ) ;
55- console . log ( `Raw ${ groupName } settings data:` , data ) ;
318+ console . log ( `Raw settings data:` , data ) ;
56319
57- // The API returns data in the format { [ groupName]: settings }
58- const settingsData = data [ groupName ] as SettingsGroupType < T > ;
320+ // Extract the correct nested data based on groupName
321+ const settingsData = extractSettingsForGroup ( data . settings , groupName ) as SettingsGroupType < T > ;
59322
60323 if ( ! settingsData ) {
61324 console . warn ( `No settings data found for group: ${ groupName } ` ) ;
@@ -185,9 +448,9 @@ const useGenericSettings = <T extends SettingsGroupName>(
185448 console . log ( `Settings from state for ${ groupName } :` , settings ) ;
186449 const { prefix, formKeys } = prefixedSettingsMap [ groupName ] ;
187450
188- // First fetch current settings to preserve values not in the form
189- console . log ( `Fetching current ${ groupName } settings before saving...` ) ;
190- const fetchResponse = await fetch ( `${ config . baseURL } /api/settings/ ${ groupName } ` , {
451+ // First fetch complete settings structure to preserve all values
452+ console . log ( `Fetching complete settings before saving ${ groupName } ...` ) ;
453+ const fetchResponse = await fetch ( `${ config . baseURL } /api/settings` , {
191454 headers : {
192455 'Authorization' : `Bearer ${ token } ` ,
193456 } ,
@@ -198,7 +461,7 @@ const useGenericSettings = <T extends SettingsGroupName>(
198461 }
199462
200463 const currentData = await fetchResponse . json ( ) ;
201- const currentSettings = currentData [ groupName ] || { } ;
464+ const currentSettings = extractSettingsForGroup ( currentData . settings , groupName ) || { } ;
202465 console . log ( `Current ${ groupName } settings from API:` , currentSettings ) ;
203466
204467 // Create a properly prefixed object for the API
@@ -231,13 +494,17 @@ const useGenericSettings = <T extends SettingsGroupName>(
231494
232495 console . log ( `Saving ${ groupName } settings:` , dataToSave ) ;
233496
234- const response = await fetch ( `${ config . baseURL } /api/settings/${ groupName } ` , {
497+ // Construct the nested update structure for the new API
498+ const nestedUpdate = buildNestedUpdate ( groupName , dataToSave ) ;
499+ console . log ( `Nested update structure:` , nestedUpdate ) ;
500+
501+ const response = await fetch ( `${ config . baseURL } /api/settings` , {
235502 method : 'POST' ,
236503 headers : {
237504 'Content-Type' : 'application/json' ,
238505 'Authorization' : `Bearer ${ token } ` ,
239506 } ,
240- body : JSON . stringify ( { [ groupName ] : dataToSave } ) ,
507+ body : JSON . stringify ( nestedUpdate ) ,
241508 } ) ;
242509
243510 if ( response . status === 401 ) {
0 commit comments