Skip to content

Commit f30f26d

Browse files
Merge pull request #47 from HORNET-Storage/feature/allowed-users
Feature/allowed users
2 parents 6713956 + 1c72d94 commit f30f26d

File tree

30 files changed

+2309
-719
lines changed

30 files changed

+2309
-719
lines changed

.claude/settings.local.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"enableAllProjectMcpServers": false
3+
}

src/api/allowedUsers.api.ts

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
import config from '@app/config/config';
2+
import { readToken } from '@app/services/localStorage.service';
3+
import {
4+
AllowedUsersSettings,
5+
AllowedUsersApiResponse,
6+
AllowedUsersNpubsResponse,
7+
BulkImportRequest,
8+
AllowedUsersNpub,
9+
DEFAULT_TIERS
10+
} from '@app/types/allowedUsers.types';
11+
12+
// Settings Management
13+
export const getAllowedUsersSettings = async (): Promise<AllowedUsersSettings> => {
14+
const token = readToken();
15+
const response = await fetch(`${config.baseURL}/api/settings/allowed_users`, {
16+
headers: {
17+
'Authorization': `Bearer ${token}`,
18+
},
19+
});
20+
21+
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
22+
23+
const text = await response.text();
24+
try {
25+
const data: AllowedUsersApiResponse = JSON.parse(text);
26+
27+
// Transform tiers from backend format to frontend format
28+
let transformedTiers = data.allowed_users.tiers.map(tier => ({
29+
data_limit: (tier as any).datalimit || tier.data_limit || '',
30+
price: tier.price
31+
}));
32+
33+
// For free mode, reconstruct full UI options with active tier marked
34+
if (data.allowed_users.mode === 'free' && transformedTiers.length === 1) {
35+
const activeTierDataLimit = transformedTiers[0].data_limit;
36+
transformedTiers = DEFAULT_TIERS.free.map(defaultTier => ({
37+
...defaultTier,
38+
active: defaultTier.data_limit === activeTierDataLimit
39+
}));
40+
}
41+
42+
const transformedSettings = {
43+
...data.allowed_users,
44+
tiers: transformedTiers
45+
};
46+
47+
return transformedSettings;
48+
} catch (jsonError) {
49+
throw new Error(`Invalid JSON response: ${text}`);
50+
}
51+
};
52+
53+
export const updateAllowedUsersSettings = async (settings: AllowedUsersSettings): Promise<{ success: boolean, message: string }> => {
54+
const token = readToken();
55+
56+
// Filter tiers based on mode - for free mode, only send active tier
57+
const tiersToSend = settings.mode === 'free'
58+
? settings.tiers.filter(tier => tier.active)
59+
: settings.tiers;
60+
61+
// Transform to nested format as expected by backend
62+
const nestedSettings = {
63+
"allowed_users": {
64+
"mode": settings.mode,
65+
"read_access": {
66+
"enabled": settings.read_access.enabled,
67+
"scope": settings.read_access.scope
68+
},
69+
"write_access": {
70+
"enabled": settings.write_access.enabled,
71+
"scope": settings.write_access.scope
72+
},
73+
"tiers": tiersToSend.map(tier => ({
74+
"datalimit": tier.data_limit || "1 GB per month", // Backend expects 'datalimit' not 'data_limit', fallback for empty values
75+
"price": tier.price || "0"
76+
}))
77+
}
78+
};
79+
80+
console.log('Sending to backend:', JSON.stringify(nestedSettings, null, 2));
81+
82+
const response = await fetch(`${config.baseURL}/api/settings/allowed_users`, {
83+
method: 'POST',
84+
headers: {
85+
'Content-Type': 'application/json',
86+
'Authorization': `Bearer ${token}`,
87+
},
88+
body: JSON.stringify(nestedSettings),
89+
});
90+
91+
const text = await response.text();
92+
console.log('Backend response:', response.status, text);
93+
94+
if (!response.ok) {
95+
console.error('Backend error:', response.status, text);
96+
throw new Error(`HTTP error! status: ${response.status}, response: ${text}`);
97+
}
98+
99+
try {
100+
return JSON.parse(text);
101+
} catch (jsonError) {
102+
throw new Error(`Invalid JSON response: ${text}`);
103+
}
104+
};
105+
106+
// Read NPUBs Management
107+
export const getReadNpubs = async (page = 1, pageSize = 20): Promise<AllowedUsersNpubsResponse> => {
108+
const token = readToken();
109+
const response = await fetch(`${config.baseURL}/api/allowed-npubs/read?page=${page}&pageSize=${pageSize}`, {
110+
headers: {
111+
'Authorization': `Bearer ${token}`,
112+
},
113+
});
114+
115+
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
116+
117+
const text = await response.text();
118+
try {
119+
const data = JSON.parse(text);
120+
// Transform backend response to expected format
121+
return {
122+
npubs: data.npubs || [],
123+
total: data.pagination?.total || 0,
124+
page: data.pagination?.page || page,
125+
pageSize: data.pagination?.pageSize || pageSize
126+
};
127+
} catch (jsonError) {
128+
throw new Error(`Invalid JSON response: ${text}`);
129+
}
130+
};
131+
132+
export const addReadNpub = async (npub: string, tier: string): Promise<{ success: boolean, message: string }> => {
133+
const token = readToken();
134+
const response = await fetch(`${config.baseURL}/api/allowed-npubs/read`, {
135+
method: 'POST',
136+
headers: {
137+
'Content-Type': 'application/json',
138+
'Authorization': `Bearer ${token}`,
139+
},
140+
body: JSON.stringify({ npub, tier }),
141+
});
142+
143+
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
144+
145+
const text = await response.text();
146+
try {
147+
return JSON.parse(text);
148+
} catch (jsonError) {
149+
throw new Error(`Invalid JSON response: ${text}`);
150+
}
151+
};
152+
153+
export const removeReadNpub = async (npub: string): Promise<{ success: boolean, message: string }> => {
154+
const token = readToken();
155+
const response = await fetch(`${config.baseURL}/api/allowed-npubs/read/${npub}`, {
156+
method: 'DELETE',
157+
headers: {
158+
'Authorization': `Bearer ${token}`,
159+
},
160+
});
161+
162+
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
163+
164+
const text = await response.text();
165+
try {
166+
return JSON.parse(text);
167+
} catch (jsonError) {
168+
throw new Error(`Invalid JSON response: ${text}`);
169+
}
170+
};
171+
172+
// Write NPUBs Management
173+
export const getWriteNpubs = async (page = 1, pageSize = 20): Promise<AllowedUsersNpubsResponse> => {
174+
const token = readToken();
175+
const response = await fetch(`${config.baseURL}/api/allowed-npubs/write?page=${page}&pageSize=${pageSize}`, {
176+
headers: {
177+
'Authorization': `Bearer ${token}`,
178+
},
179+
});
180+
181+
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
182+
183+
const text = await response.text();
184+
try {
185+
const data = JSON.parse(text);
186+
// Transform backend response to expected format
187+
return {
188+
npubs: data.npubs || [],
189+
total: data.pagination?.total || 0,
190+
page: data.pagination?.page || page,
191+
pageSize: data.pagination?.pageSize || pageSize
192+
};
193+
} catch (jsonError) {
194+
throw new Error(`Invalid JSON response: ${text}`);
195+
}
196+
};
197+
198+
export const addWriteNpub = async (npub: string, tier: string): Promise<{ success: boolean, message: string }> => {
199+
const token = readToken();
200+
const response = await fetch(`${config.baseURL}/api/allowed-npubs/write`, {
201+
method: 'POST',
202+
headers: {
203+
'Content-Type': 'application/json',
204+
'Authorization': `Bearer ${token}`,
205+
},
206+
body: JSON.stringify({ npub, tier }),
207+
});
208+
209+
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
210+
211+
const text = await response.text();
212+
try {
213+
return JSON.parse(text);
214+
} catch (jsonError) {
215+
throw new Error(`Invalid JSON response: ${text}`);
216+
}
217+
};
218+
219+
export const removeWriteNpub = async (npub: string): Promise<{ success: boolean, message: string }> => {
220+
const token = readToken();
221+
const response = await fetch(`${config.baseURL}/api/allowed-npubs/write/${npub}`, {
222+
method: 'DELETE',
223+
headers: {
224+
'Authorization': `Bearer ${token}`,
225+
},
226+
});
227+
228+
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
229+
230+
const text = await response.text();
231+
try {
232+
return JSON.parse(text);
233+
} catch (jsonError) {
234+
throw new Error(`Invalid JSON response: ${text}`);
235+
}
236+
};
237+
238+
// Bulk Import
239+
export const bulkImportNpubs = async (importData: BulkImportRequest): Promise<{ success: boolean, message: string, imported: number, failed: number }> => {
240+
const token = readToken();
241+
const response = await fetch(`${config.baseURL}/api/allowed-npubs/bulk-import`, {
242+
method: 'POST',
243+
headers: {
244+
'Content-Type': 'application/json',
245+
'Authorization': `Bearer ${token}`,
246+
},
247+
body: JSON.stringify(importData),
248+
});
249+
250+
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
251+
252+
const text = await response.text();
253+
try {
254+
return JSON.parse(text);
255+
} catch (jsonError) {
256+
throw new Error(`Invalid JSON response: ${text}`);
257+
}
258+
};

0 commit comments

Comments
 (0)