Highly ambitious ATProtocol AppView service and sdks

add slice domain validation

+61 -4
+4 -2
frontend/src/features/dashboard/handlers.tsx
··· 7 7 import { CreateSliceDialog } from "./templates/fragments/CreateSliceDialog.tsx"; 8 8 import { getSliceActor, getSlicesForActor } from "../../lib/api.ts"; 9 9 import { buildSliceUrl } from "../../utils/slice-params.ts"; 10 + import { validateDomain } from "../../utils/validation.ts"; 10 11 11 12 async function handleProfilePage( 12 13 req: Request, ··· 81 82 ); 82 83 } 83 84 84 - if (!domain || domain.trim().length === 0) { 85 + const domainError = validateDomain(domain); 86 + if (domainError) { 85 87 return renderHTML( 86 88 <CreateSliceDialog 87 - error="Primary domain is required" 89 + error={domainError} 88 90 name={name} 89 91 domain={domain} 90 92 />
+14 -2
frontend/src/features/slices/settings/handlers.tsx
··· 9 9 withSliceAccess, 10 10 } from "../../../routes/slice-middleware.ts"; 11 11 import { extractSliceParams } from "../../../utils/slice-params.ts"; 12 + import { validateDomain } from "../../../utils/validation.ts"; 12 13 import { SliceSettings } from "./templates/SliceSettings.tsx"; 13 14 14 15 async function handleSliceSettingsPage( ··· 34 35 const updated = url.searchParams.get("updated"); 35 36 const cleared = url.searchParams.get("cleared"); 36 37 const error = url.searchParams.get("error"); 38 + const message = url.searchParams.get("message"); 37 39 38 40 return renderHTML( 39 41 <SliceSettings ··· 42 44 updated={updated === "true"} 43 45 cleared={cleared === "true"} 44 46 error={error} 47 + errorMessage={message} 45 48 currentUser={authContext.currentUser} 46 49 hasSliceAccess={context.sliceContext?.hasAccess} 47 50 /> ··· 72 75 return new Response("Name is required", { status: 400 }); 73 76 } 74 77 75 - if (!domain || domain.trim().length === 0) { 76 - return new Response("Domain is required", { status: 400 }); 78 + const domainError = validateDomain(domain); 79 + if (domainError) { 80 + const errorParam = domainError === "Domain is required" 81 + ? "domain_required" 82 + : domainError.includes("is not supported") 83 + ? "unsupported_domain" 84 + : "invalid_domain_format"; 85 + 86 + return hxRedirect( 87 + `/profile/${context.currentUser.handle}/slice/${sliceId}/settings?error=${errorParam}&message=${encodeURIComponent(domainError)}` 88 + ); 77 89 } 78 90 79 91 // Construct the URI for this slice
+8
frontend/src/features/slices/settings/templates/SliceSettings.tsx
··· 13 13 updated?: boolean; 14 14 cleared?: boolean; 15 15 error?: string | null; 16 + errorMessage?: string | null; 16 17 currentUser?: AuthenticatedUser; 17 18 hasSliceAccess?: boolean; 18 19 } ··· 23 24 updated = false, 24 25 cleared = false, 25 26 error = null, 27 + errorMessage = null, 26 28 currentUser, 27 29 hasSliceAccess, 28 30 }: SliceSettingsProps) { ··· 60 62 ? "Failed to update slice settings. Please try again." 61 63 : error === "clear_failed" 62 64 ? "Failed to clear records. Please try again." 65 + : error === "domain_required" 66 + ? "Domain is required." 67 + : error === "unsupported_domain" && errorMessage 68 + ? errorMessage 69 + : error === "invalid_domain_format" 70 + ? "Domain must be a valid format (e.g. social.grain)." 63 71 : "An error occurred." 64 72 } 65 73 />
+35
frontend/src/utils/validation.ts
··· 1 + // List of domains that are not supported for slices 2 + const UNSUPPORTED_DOMAINS = [ 3 + "app.bsky", 4 + "com.atproto", 5 + ]; 6 + 7 + export function validateDomain(domain: string): string | null { 8 + const trimmedDomain = domain.trim(); 9 + 10 + if (!trimmedDomain || trimmedDomain.length === 0) { 11 + return "Domain is required"; 12 + } 13 + 14 + if (UNSUPPORTED_DOMAINS.includes(trimmedDomain)) { 15 + return `The domain '${trimmedDomain}' is not supported`; 16 + } 17 + 18 + // Domain must have at least two parts (e.g. social.grain) 19 + if (!trimmedDomain.includes('.')) { 20 + return "Domain must have at least two parts (e.g. social.grain)"; 21 + } 22 + 23 + // Validate domain format 24 + const domainRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)+$/; 25 + if (!domainRegex.test(trimmedDomain)) { 26 + return "Domain must be a valid format (e.g. social.grain)"; 27 + } 28 + 29 + return null; // Valid 30 + } 31 + 32 + // Function to get the list of unsupported domains (for debugging/admin purposes) 33 + export function getUnsupportedDomains(): readonly string[] { 34 + return UNSUPPORTED_DOMAINS; 35 + }