+108
-58
frontend/src/features/slices/sync/handlers.tsx
+108
-58
frontend/src/features/slices/sync/handlers.tsx
···
2
2
import { renderHTML } from "../../../utils/render.tsx";
3
3
import { requireAuth, withAuth } from "../../../routes/middleware.ts";
4
4
import { getSliceClient } from "../../../utils/client.ts";
5
-
import { buildSliceUri } from "../../../utils/at-uri.ts";
6
-
import { publicClient } from "../../../config.ts";
7
5
import {
8
6
requireSliceAccess,
9
7
withSliceAccess,
···
23
21
req: Request,
24
22
params?: URLPatternResult
25
23
): Promise<Response> {
26
-
const context = await withAuth(req);
27
-
const authResponse = requireAuth(context);
24
+
const authContext = await withAuth(req);
25
+
const authResponse = requireAuth(authContext);
28
26
if (authResponse) return authResponse;
29
27
30
-
const sliceId = params?.pathname.groups.id;
31
-
32
-
if (!sliceId) {
33
-
return renderHTML(<SyncResult success={false} error="Invalid slice ID" />);
28
+
const sliceParams = extractSliceParams(params);
29
+
if (!sliceParams) {
30
+
return renderHTML(
31
+
<SyncResult success={false} error="Invalid slice parameters" />
32
+
);
34
33
}
35
34
35
+
const context = await withSliceAccess(
36
+
authContext,
37
+
sliceParams.handle,
38
+
sliceParams.sliceId
39
+
);
40
+
const accessError = requireSliceAccess(context);
41
+
if (accessError) return accessError;
42
+
36
43
try {
37
44
const formData = await req.formData();
38
45
const collections = formData.getAll("collections") as string[];
···
55
62
);
56
63
}
57
64
58
-
const sliceClient = getSliceClient(context, sliceId);
65
+
const sliceClient = getSliceClient(
66
+
authContext,
67
+
sliceParams.sliceId,
68
+
context.sliceContext!.profileDid
69
+
);
59
70
await sliceClient.network.slices.slice.startSync({
60
-
slice: buildSliceUri(context.currentUser.sub!, sliceId),
71
+
slice: context.sliceContext!.sliceUri,
61
72
collections: collections.length > 0 ? collections : undefined,
62
73
externalCollections:
63
74
externalCollections.length > 0 ? externalCollections : undefined,
64
75
repos: repos.length > 0 ? repos : undefined,
65
76
});
66
77
67
-
const handle = context.currentUser?.handle;
68
-
if (!handle) {
69
-
throw new Error("Unable to determine user handle");
70
-
}
71
-
72
-
const redirectUrl = buildSliceUrl(handle, sliceId, "sync");
78
+
const redirectUrl = buildSliceUrl(
79
+
sliceParams.handle,
80
+
sliceParams.sliceId,
81
+
"sync"
82
+
);
73
83
return hxRedirect(redirectUrl);
74
84
} catch (error) {
75
85
console.error("Failed to start sync:", error);
···
82
92
req: Request,
83
93
params?: URLPatternResult
84
94
): Promise<Response> {
85
-
const context = await withAuth(req);
86
-
const authResponse = requireAuth(context);
95
+
const authContext = await withAuth(req);
96
+
const authResponse = requireAuth(authContext);
87
97
if (authResponse) return authResponse;
88
98
89
-
const sliceId = params?.pathname.groups.id;
90
-
91
-
if (!sliceId) {
99
+
const sliceParams = extractSliceParams(params);
100
+
if (!sliceParams) {
92
101
return renderHTML(
93
-
<div className="p-8 text-center text-red-600">Invalid slice ID</div>,
102
+
<div className="p-8 text-center text-red-600">
103
+
Invalid slice parameters
104
+
</div>,
94
105
{ status: 400 }
95
106
);
96
107
}
97
108
98
-
// Extract handle from query parameters
99
-
const url = new URL(req.url);
100
-
const handle = url.searchParams.get("handle");
109
+
const context = await withSliceAccess(
110
+
authContext,
111
+
sliceParams.handle,
112
+
sliceParams.sliceId
113
+
);
114
+
const accessError = requireSliceAccess(context);
115
+
if (accessError) return accessError;
101
116
102
117
try {
103
-
const sliceUri = buildSliceUri(context.currentUser.sub!, sliceId);
104
-
const sliceClient = getSliceClient(context, sliceId);
118
+
const sliceClient = getSliceClient(
119
+
authContext,
120
+
sliceParams.sliceId,
121
+
context.sliceContext!.profileDid
122
+
);
105
123
const jobsResponse = await sliceClient.network.slices.slice.getJobHistory({
106
-
userDid: context.currentUser.sub!,
107
-
sliceUri: sliceUri,
124
+
userDid: authContext.currentUser.sub!,
125
+
sliceUri: context.sliceContext!.sliceUri,
108
126
limit: 10,
109
127
});
110
128
111
129
return renderHTML(
112
130
<JobHistory
113
131
jobs={jobsResponse || []}
114
-
sliceId={sliceId}
115
-
handle={handle || undefined}
132
+
sliceId={sliceParams.sliceId}
133
+
handle={sliceParams.handle}
116
134
/>
117
135
);
118
136
} catch (error) {
···
191
209
req: Request,
192
210
params?: URLPatternResult
193
211
): Promise<Response> {
194
-
const context = await withAuth(req);
195
-
const authResponse = requireAuth(context);
212
+
const authContext = await withAuth(req);
213
+
const authResponse = requireAuth(authContext);
196
214
if (authResponse) return authResponse;
197
215
198
-
const sliceId = params?.pathname.groups.id;
199
-
if (!sliceId) {
200
-
return new Response("Invalid slice ID", { status: 400 });
216
+
const sliceParams = extractSliceParams(params);
217
+
if (!sliceParams) {
218
+
return new Response("Invalid slice parameters", { status: 400 });
201
219
}
202
220
221
+
const context = await withSliceAccess(
222
+
authContext,
223
+
sliceParams.handle,
224
+
sliceParams.sliceId
225
+
);
226
+
const accessError = requireSliceAccess(context);
227
+
if (accessError) return accessError;
228
+
203
229
try {
204
-
const sliceClient = getSliceClient(context, sliceId);
230
+
const sliceClient = getSliceClient(
231
+
authContext,
232
+
sliceParams.sliceId,
233
+
context.sliceContext!.profileDid
234
+
);
205
235
const collections: string[] = [];
206
236
const externalCollections: string[] = [];
207
237
208
-
// Get slice info for domain comparison
209
-
const sliceUri = buildSliceUri(context.currentUser.sub!, sliceId);
210
-
const sliceRecord = await publicClient.network.slices.slice.getRecord({
211
-
uri: sliceUri,
212
-
});
213
-
const sliceDomain = sliceRecord.value.domain;
238
+
// Get slice domain from context
239
+
const sliceDomain = context.sliceContext!.slice!.domain;
214
240
215
241
// Get all lexicons and filter by record types
216
242
try {
···
241
267
242
268
return renderHTML(
243
269
<SyncFormModal
244
-
sliceId={sliceId}
270
+
sliceId={sliceParams.sliceId}
271
+
handle={sliceParams.handle}
245
272
collections={collections}
246
273
externalCollections={externalCollections}
247
274
/>
248
275
);
249
276
} catch (error) {
250
277
console.error("Error loading sync modal:", error);
251
-
return renderHTML(<SyncFormModal sliceId={sliceId} />);
278
+
return renderHTML(
279
+
<SyncFormModal
280
+
sliceId={sliceParams.sliceId}
281
+
handle={sliceParams.handle}
282
+
/>
283
+
);
252
284
}
253
285
}
254
286
···
256
288
req: Request,
257
289
params?: URLPatternResult
258
290
): Promise<Response> {
259
-
const context = await withAuth(req);
260
-
const authResponse = requireAuth(context);
291
+
const authContext = await withAuth(req);
292
+
const authResponse = requireAuth(authContext);
261
293
if (authResponse) return authResponse;
262
294
263
-
const sliceId = params?.pathname.groups.id;
264
-
if (!sliceId) {
265
-
return new Response("Invalid slice ID", { status: 400 });
295
+
const sliceParams = extractSliceParams(params);
296
+
if (!sliceParams) {
297
+
return new Response("Invalid slice parameters", { status: 400 });
266
298
}
299
+
300
+
const context = await withSliceAccess(
301
+
authContext,
302
+
sliceParams.handle,
303
+
sliceParams.sliceId
304
+
);
305
+
const accessError = requireSliceAccess(context);
306
+
if (accessError) return accessError;
267
307
268
308
try {
269
309
const formData = await req.formData();
···
287
327
);
288
328
}
289
329
290
-
const sliceClient = getSliceClient(context, sliceId);
291
-
const sliceUri = buildSliceUri(context.currentUser.sub!, sliceId);
330
+
const sliceClient = getSliceClient(
331
+
authContext,
332
+
sliceParams.sliceId,
333
+
context.sliceContext!.profileDid
334
+
);
292
335
293
336
// Call the getSyncSummary endpoint
294
337
const requestParams = {
295
-
slice: sliceUri,
338
+
slice: context.sliceContext!.sliceUri,
296
339
collections: collections.length > 0 ? collections : undefined,
297
340
externalCollections:
298
341
externalCollections.length > 0 ? externalCollections : undefined,
···
304
347
305
348
return renderHTML(
306
349
<SyncSummaryModal
307
-
sliceId={sliceId}
350
+
sliceId={sliceParams.sliceId}
351
+
handle={sliceParams.handle}
308
352
summary={summaryResponse}
309
353
collections={collections}
310
354
externalCollections={externalCollections}
···
326
370
},
327
371
{
328
372
method: "GET",
329
-
pattern: new URLPattern({ pathname: "/api/slices/:id/sync/modal" }),
373
+
pattern: new URLPattern({
374
+
pathname: "/profile/:handle/slice/:rkey/sync/modal",
375
+
}),
330
376
handler: handleShowSyncModal,
331
377
},
332
378
{
333
379
method: "POST",
334
-
pattern: new URLPattern({ pathname: "/api/slices/:id/sync" }),
380
+
pattern: new URLPattern({ pathname: "/profile/:handle/slice/:rkey/sync" }),
335
381
handler: handleSliceSync,
336
382
},
337
383
{
338
384
method: "POST",
339
-
pattern: new URLPattern({ pathname: "/api/slices/:id/sync/summary" }),
385
+
pattern: new URLPattern({
386
+
pathname: "/profile/:handle/slice/:rkey/sync/summary",
387
+
}),
340
388
handler: handleSyncSummary,
341
389
},
342
390
{
343
391
method: "GET",
344
-
pattern: new URLPattern({ pathname: "/api/slices/:id/job-history" }),
392
+
pattern: new URLPattern({
393
+
pathname: "/profile/:handle/slice/:rkey/job-history",
394
+
}),
345
395
handler: handleJobHistory,
346
396
},
347
397
];
+2
-2
frontend/src/features/slices/sync/templates/SliceSyncPage.tsx
+2
-2
frontend/src/features/slices/sync/templates/SliceSyncPage.tsx
···
31
31
<div className="flex justify-end mb-4">
32
32
<Button
33
33
variant="success"
34
-
hx-get={`/api/slices/${sliceId}/sync/modal`}
34
+
hx-get={`/profile/${slice.creator?.handle}/slice/${sliceId}/sync/modal`}
35
35
hx-target="#modal-container"
36
36
hx-swap="innerHTML"
37
37
>
···
41
41
<Card>
42
42
<Card.Header title="Recent Sync History" />
43
43
<Card.Content
44
-
hx-get={`/api/slices/${sliceId}/job-history?handle=${slice.creator?.handle}`}
44
+
hx-get={`/profile/${slice.creator?.handle}/slice/${sliceId}/job-history`}
45
45
hx-trigger="load, every 10s"
46
46
hx-swap="innerHTML"
47
47
>
+3
-1
frontend/src/features/slices/sync/templates/fragments/SyncFormModal.tsx
+3
-1
frontend/src/features/slices/sync/templates/fragments/SyncFormModal.tsx
···
5
5
6
6
interface SyncFormModalProps {
7
7
sliceId: string;
8
+
handle: string;
8
9
collections?: string[];
9
10
externalCollections?: string[];
10
11
}
11
12
12
13
export function SyncFormModal({
13
14
sliceId,
15
+
handle,
14
16
collections = [],
15
17
externalCollections = [],
16
18
}: SyncFormModalProps) {
···
95
97
type="submit"
96
98
variant="primary"
97
99
className="flex items-center justify-center"
98
-
hx-post={`/api/slices/${sliceId}/sync/summary`}
100
+
hx-post={`/profile/${handle}/slice/${sliceId}/sync/summary`}
99
101
hx-target="#modal-container"
100
102
hx-swap="innerHTML"
101
103
>
+3
-1
frontend/src/features/slices/sync/templates/fragments/SyncSummaryModal.tsx
+3
-1
frontend/src/features/slices/sync/templates/fragments/SyncSummaryModal.tsx
···
5
5
6
6
interface SyncSummaryModalProps {
7
7
sliceId: string;
8
+
handle: string;
8
9
summary: NetworkSlicesSliceGetSyncSummaryOutput;
9
10
collections: string[];
10
11
externalCollections: string[];
···
13
14
14
15
export function SyncSummaryModal({
15
16
sliceId,
17
+
handle,
16
18
summary,
17
19
collections,
18
20
externalCollections,
···
144
146
145
147
{/* Actions */}
146
148
<form
147
-
hx-post={`/api/slices/${sliceId}/sync`}
149
+
hx-post={`/profile/${handle}/slice/${sliceId}/sync`}
148
150
hx-target="#sync-result"
149
151
hx-swap="innerHTML"
150
152
className="space-y-4"