Skip to content

Commit a8439d9

Browse files
committed
Implement backend API migration and file size limit controls
- Migrate useRelaySettings and useChartData hooks to new backend API structure - Add core kinds protection system to prevent removal of essential event types - Implement file size limit controls for photos, videos, and audio - Add FileSizeLimitInput component with proper theme integration - Update MediaSection to include configurable file size inputs - Create transformation functions for new backend settings format - Support dynamic media types and chart data from backend configuration - Add TypeScript interfaces for new API structure
1 parent d852a02 commit a8439d9

10 files changed

Lines changed: 562 additions & 98 deletions

File tree

src/components/relay-settings/layouts/DesktopLayout.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,26 @@ interface DesktopLayoutProps {
4646
photos: {
4747
selected: string[];
4848
isActive: boolean;
49+
maxSizeMB: number;
4950
onChange: (values: string[]) => void;
5051
onToggle: (checked: boolean) => void;
52+
onMaxSizeChange: (size: number) => void;
5153
};
5254
videos: {
5355
selected: string[];
5456
isActive: boolean;
57+
maxSizeMB: number;
5558
onChange: (values: string[]) => void;
5659
onToggle: (checked: boolean) => void;
60+
onMaxSizeChange: (size: number) => void;
5761
};
5862
audio: {
5963
selected: string[];
6064
isActive: boolean;
65+
maxSizeMB: number;
6166
onChange: (values: string[]) => void;
6267
onToggle: (checked: boolean) => void;
68+
onMaxSizeChange: (size: number) => void;
6369
};
6470
// Moderation section props
6571
moderationMode: string;

src/components/relay-settings/layouts/MobileLayout.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,26 @@ interface MobileLayoutProps {
4343
photos: {
4444
selected: string[];
4545
isActive: boolean;
46+
maxSizeMB: number;
4647
onChange: (values: string[]) => void;
4748
onToggle: (checked: boolean) => void;
49+
onMaxSizeChange: (size: number) => void;
4850
};
4951
videos: {
5052
selected: string[];
5153
isActive: boolean;
54+
maxSizeMB: number;
5255
onChange: (values: string[]) => void;
5356
onToggle: (checked: boolean) => void;
57+
onMaxSizeChange: (size: number) => void;
5458
};
5559
audio: {
5660
selected: string[];
5761
isActive: boolean;
62+
maxSizeMB: number;
5863
onChange: (values: string[]) => void;
5964
onToggle: (checked: boolean) => void;
65+
onMaxSizeChange: (size: number) => void;
6066
};
6167
// Moderation section props
6268
moderationMode: string;

src/components/relay-settings/sections/MediaSection/MediaSection.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,33 @@ import * as S from '@app/pages/uiComponentsPages/UIComponentsPage.styles';
55
import { CollapsibleSection } from '../../shared/CollapsibleSection/CollapsibleSection';
66
import { MediaTypeList } from './components/MediaTypeList';
77
import { MediaToggle } from './components/MediaToggle';
8+
import { FileSizeLimitInput } from './components/FileSizeLimitInput';
89

910
export interface MediaSectionProps {
1011
mode: string;
1112
photos: {
1213
selected: string[];
1314
isActive: boolean;
15+
maxSizeMB: number;
1416
onChange: (values: string[]) => void;
1517
onToggle: (checked: boolean) => void;
18+
onMaxSizeChange: (size: number) => void;
1619
};
1720
videos: {
1821
selected: string[];
1922
isActive: boolean;
23+
maxSizeMB: number;
2024
onChange: (values: string[]) => void;
2125
onToggle: (checked: boolean) => void;
26+
onMaxSizeChange: (size: number) => void;
2227
};
2328
audio: {
2429
selected: string[];
2530
isActive: boolean;
31+
maxSizeMB: number;
2632
onChange: (values: string[]) => void;
2733
onToggle: (checked: boolean) => void;
34+
onMaxSizeChange: (size: number) => void;
2835
};
2936
}
3037

@@ -87,6 +94,15 @@ export const MediaSection: React.FC<MediaSectionProps> = ({
8794
onChange={photos.onToggle}
8895
mode={mode}
8996
/>
97+
<FileSizeLimitInput
98+
label="Maximum Photo Size"
99+
value={photos.maxSizeMB}
100+
onChange={photos.onMaxSizeChange}
101+
min={1}
102+
max={1000}
103+
description="Set the maximum file size limit for photo uploads"
104+
disabled={!photos.isActive}
105+
/>
90106
<MediaTypeList
91107
formats={imageFormats}
92108
selectedFormats={photos.selected}
@@ -104,6 +120,15 @@ export const MediaSection: React.FC<MediaSectionProps> = ({
104120
onChange={videos.onToggle}
105121
mode={mode}
106122
/>
123+
<FileSizeLimitInput
124+
label="Maximum Video Size"
125+
value={videos.maxSizeMB}
126+
onChange={videos.onMaxSizeChange}
127+
min={1}
128+
max={5000}
129+
description="Set the maximum file size limit for video uploads"
130+
disabled={!videos.isActive}
131+
/>
107132
<MediaTypeList
108133
formats={videoFormats}
109134
selectedFormats={videos.selected}
@@ -121,6 +146,15 @@ export const MediaSection: React.FC<MediaSectionProps> = ({
121146
onChange={audio.onToggle}
122147
mode={mode}
123148
/>
149+
<FileSizeLimitInput
150+
label="Maximum Audio Size"
151+
value={audio.maxSizeMB}
152+
onChange={audio.onMaxSizeChange}
153+
min={1}
154+
max={500}
155+
description="Set the maximum file size limit for audio uploads"
156+
disabled={!audio.isActive}
157+
/>
124158
<MediaTypeList
125159
formats={audioFormats}
126160
selectedFormats={audio.selected}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// src/components/relay-settings/sections/MediaSection/components/FileSizeLimitInput.tsx
2+
3+
import React from 'react';
4+
import { BaseForm } from '@app/components/common/forms/BaseForm/BaseForm';
5+
import { InputNumber, Space } from 'antd';
6+
import styled from 'styled-components';
7+
8+
const StyledContainer = styled.div`
9+
margin: 16px 0;
10+
padding: 12px;
11+
background: var(--secondary-background-color);
12+
border-radius: 8px;
13+
border: 1px solid var(--border-color);
14+
`;
15+
16+
const StyledLabel = styled.div`
17+
font-size: 14px;
18+
font-weight: 500;
19+
color: var(--text-main-color);
20+
margin-bottom: 8px;
21+
`;
22+
23+
const StyledDescription = styled.div`
24+
font-size: 12px;
25+
color: var(--text-light-color);
26+
margin-bottom: 12px;
27+
`;
28+
29+
const StyledInputWrapper = styled.div`
30+
.ant-input-number {
31+
width: 120px;
32+
background: var(--background-color);
33+
border-color: var(--border-color);
34+
color: var(--text-main-color);
35+
36+
&:hover {
37+
border-color: var(--primary-color);
38+
}
39+
40+
&:focus {
41+
border-color: var(--primary-color);
42+
box-shadow: 0 0 0 2px var(--primary-color)20;
43+
}
44+
}
45+
46+
.ant-input-number-input {
47+
color: var(--text-main-color);
48+
}
49+
`;
50+
51+
const StyledUnit = styled.span`
52+
font-size: 14px;
53+
color: var(--text-light-color);
54+
margin-left: 8px;
55+
`;
56+
57+
export interface FileSizeLimitInputProps {
58+
label: string;
59+
value: number;
60+
onChange: (value: number) => void;
61+
min?: number;
62+
max?: number;
63+
step?: number;
64+
description?: string;
65+
disabled?: boolean;
66+
}
67+
68+
export const FileSizeLimitInput: React.FC<FileSizeLimitInputProps> = ({
69+
label,
70+
value,
71+
onChange,
72+
min = 1,
73+
max = 5000,
74+
step = 1,
75+
description,
76+
disabled = false,
77+
}) => {
78+
const handleChange = (newValue: number | null) => {
79+
if (newValue !== null && newValue >= min && newValue <= max) {
80+
onChange(newValue);
81+
}
82+
};
83+
84+
return (
85+
<StyledContainer>
86+
<StyledLabel>{label}</StyledLabel>
87+
{description && <StyledDescription>{description}</StyledDescription>}
88+
<StyledInputWrapper>
89+
<Space align="center">
90+
<InputNumber
91+
value={value}
92+
onChange={handleChange}
93+
min={min}
94+
max={max}
95+
step={step}
96+
disabled={disabled}
97+
placeholder="Enter size"
98+
precision={0}
99+
/>
100+
<StyledUnit>MB</StyledUnit>
101+
</Space>
102+
</StyledInputWrapper>
103+
</StyledContainer>
104+
);
105+
};
106+
107+
export default FileSizeLimitInput;

src/constants/coreKinds.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Core kinds that are essential for relay operation and cannot be removed
2+
export const CORE_KINDS = [
3+
'kind0', // User profiles - Required for user management (CRITICAL - relay unusable without)
4+
'kind22242', // Auth events - Required for NIP-42 authentication
5+
'kind10010', // Mute list - Required for content filtering/mute words
6+
'kind19841', // Storage manifest - Required for file tracking
7+
'kind19842', // Storage metadata - Required for file info
8+
'kind19843', // Storage delete - Required for file cleanup
9+
];
10+
11+
// Optional kinds that can be toggled (kind1 can be removed if needed)
12+
export const OPTIONAL_KINDS = [
13+
'kind1', // Text notes - Core functionality but can be disabled if needed
14+
];
15+
16+
// Helper function to ensure core kinds are always included
17+
export const ensureCoreKinds = (kindList: string[]): string[] => {
18+
const combined = [...kindList, ...CORE_KINDS];
19+
return Array.from(new Set(combined));
20+
};
21+
22+
// Helper function to check if a kind is protected
23+
export const isCoreKind = (kind: string): boolean => {
24+
return CORE_KINDS.includes(kind);
25+
};

src/constants/relaySettings.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,13 @@ export type Settings = {
1616
isAudioActive: boolean;
1717
isFileStorageActive: boolean;
1818
moderationMode: string; // "strict" or "passive"
19+
// File size limits in MB
20+
photoMaxSizeMB: number;
21+
videoMaxSizeMB: number;
22+
audioMaxSizeMB: number;
1923
}
2024

21-
export type Category = 'kinds' | 'photos' | 'videos' | 'gitNestr' | 'audio' | 'dynamicKinds' | 'appBuckets' | 'dynamicAppBuckets';
25+
export type Category = 'kinds' | 'photos' | 'videos' | 'gitNestr' | 'audio' | 'dynamicKinds' | 'appBuckets' | 'dynamicAppBuckets' | 'photoMaxSizeMB' | 'videoMaxSizeMB' | 'audioMaxSizeMB';
2226
export const noteOptions = [
2327
{ kind: 0, kindString: 'kind0', description: 'Metadata', category: 1 },
2428
{ kind: 1, kindString: 'kind1', description: 'Text Note', category: 1 },
@@ -43,6 +47,12 @@ export const noteOptions = [
4347
{ kind: 10011, kindString: 'kind10011', description: 'Issue Notes', category: 3 },
4448
{ kind: 10022, kindString: 'kind10022', description: 'PR Notes', category: 3 },
4549
{ kind: 9803, kindString: 'kind9803', description: 'Commit Notes', category: 3 },
50+
// Core kinds essential for relay operation
51+
{ kind: 10010, kindString: 'kind10010', description: 'Content Filtering', category: 1 },
52+
{ kind: 22242, kindString: 'kind22242', description: 'NIP-42 Auth Events', category: 1 },
53+
{ kind: 19841, kindString: 'kind19841', description: 'Storage Manifest', category: 1 },
54+
{ kind: 19842, kindString: 'kind19842', description: 'Storage Metadata', category: 1 },
55+
{ kind: 19843, kindString: 'kind19843', description: 'Storage Delete', category: 1 },
4656
];
4757
export const appBuckets = [
4858
{ id: 'nostr', label: 'Nostr' },

src/hooks/useChartData.ts

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import config from '@app/config/config';
55
import { readToken } from '@app/services/localStorage.service';
66
import { message } from 'antd';
77
import { useHandleLogout } from './authUtils';
8+
import { FileCountResponse } from '@app/types/newSettings.types';
89

910
interface ChartDataItem {
1011
value: number;
@@ -43,16 +44,52 @@ const useChartData = () => {
4344
throw new Error(`Network response was not ok (status: ${response.status})`);
4445
}
4546

46-
const data = await response.json();
47+
const data: FileCountResponse = await response.json();
4748

4849
// Process the data into chartDataItems using translated names
49-
const newChartData: ChartDataItem[] = [
50-
{ value: data.kinds, name: t('categories.kinds') },
51-
{ value: data.photos, name: t('categories.photos') },
52-
{ value: data.videos, name: t('categories.videos') },
53-
{ value: data.audio, name: t('categories.audio') },
54-
{ value: data.misc, name: t('categories.misc') },
55-
];
50+
// Handle dynamic media types while maintaining UI compatibility
51+
const newChartData: ChartDataItem[] = [];
52+
53+
// Always include kinds first
54+
newChartData.push({ value: data.kinds, name: t('categories.kinds') });
55+
56+
// Destructure to separate kinds from media types
57+
const { kinds, ...mediaCounts } = data;
58+
59+
// Map dynamic media types to chart data with fallback translations
60+
Object.entries(mediaCounts).forEach(([mediaType, count]) => {
61+
let translationKey = '';
62+
let fallbackName = '';
63+
64+
// Map new media types to existing translation keys for UI compatibility
65+
switch (mediaType) {
66+
case 'image':
67+
translationKey = 'categories.photos';
68+
fallbackName = 'Photos';
69+
break;
70+
case 'video':
71+
translationKey = 'categories.videos';
72+
fallbackName = 'Videos';
73+
break;
74+
case 'audio':
75+
translationKey = 'categories.audio';
76+
fallbackName = 'Audio';
77+
break;
78+
case 'git':
79+
translationKey = 'categories.misc';
80+
fallbackName = 'Git';
81+
break;
82+
default:
83+
// For any new media types not yet in translations
84+
translationKey = `categories.${mediaType}`;
85+
fallbackName = mediaType.charAt(0).toUpperCase() + mediaType.slice(1);
86+
break;
87+
}
88+
89+
// Use translation if available, otherwise use fallback
90+
const displayName = t(translationKey, { defaultValue: fallbackName });
91+
newChartData.push({ value: count, name: displayName });
92+
});
5693

5794
setChartData(newChartData);
5895
} catch (error) {

0 commit comments

Comments
 (0)