feat: integrate R2 for secure build artifact downloads and update flashing flow with authentication

This commit is contained in:
Ben Allfree
2025-11-24 05:44:59 -08:00
parent 9cc1a96c1a
commit 60cf17b584
12 changed files with 344 additions and 14 deletions
-4
View File
@@ -24,10 +24,6 @@ function App() {
<Routes>
<Route path="/" element={<LandingPage />} />
<Route path="/profiles/:id" element={<ProfileDetail />} />
<Route
path="/profiles/:id/flash/:target"
element={<ProfileFlash />}
/>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</Unauthenticated>
+3 -1
View File
@@ -31,7 +31,9 @@ export default function Navbar() {
<div className="flex items-center gap-4">
<Unauthenticated>
<Button
onClick={() => signIn('google', { redirectTo: '/dashboard' })}
onClick={() =>
signIn('google', { redirectTo: window.location.href })
}
className="bg-white text-slate-900 hover:bg-slate-200"
>
Sign in
+1 -1
View File
@@ -30,7 +30,7 @@ export function ProfileStatisticPills({
}
interface ProfileCardContentProps {
profile: Doc
profile: Doc<'profiles'>
}
export function ProfileCardContent({ profile }: ProfileCardContentProps) {
+1 -1
View File
@@ -18,7 +18,7 @@ interface ProfileFormValues {
}
interface ProfileEditorProps {
initialData?: Doc
initialData?: Doc<'profiles'>
onSave: () => void
onCancel: () => void
}
+1 -1
View File
@@ -18,7 +18,7 @@ export default function Dashboard() {
const removeProfile = useMutation(api.profiles.remove)
const [isCreating, setIsCreating] = useState(false)
const handleEdit = (profile: Doc) => {
const handleEdit = (profile: Doc<'profiles'>) => {
navigate(`/dashboard/profiles/${profile._id}`)
}
+9 -1
View File
@@ -1,4 +1,5 @@
import { useMutation, useQuery } from 'convex/react'
import { useAuthActions } from '@convex-dev/auth/react'
import { useConvexAuth, useMutation, useQuery } from 'convex/react'
import * as React from 'react'
import { useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
@@ -13,6 +14,8 @@ import { TARGETS } from '../constants/targets'
export default function ProfileDetail() {
const { id } = useParams<{ id: string }>()
const navigate = useNavigate()
const { isAuthenticated } = useConvexAuth()
const { signIn } = useAuthActions()
const triggerBuildViaProfile = useMutation(api.builds.triggerBuildViaProfile)
const profile = useQuery(
api.profiles.get,
@@ -81,6 +84,11 @@ export default function ProfileDetail() {
const handleFlash = async () => {
if (!selectedTarget || !id) return
if (!isAuthenticated) {
void signIn('google', { redirectTo: window.location.href })
return
}
try {
await triggerBuildViaProfile({
profileId: id as Id<'profiles'>,
+7 -5
View File
@@ -29,7 +29,7 @@ export default function ProfileFlash() {
api.profiles.get,
id ? { id: id as Id<'profiles'> } : 'skip'
)
const recordFlash = useMutation(api.profiles.recordFlash)
const generateDownloadUrl = useMutation(api.builds.generateDownloadUrl)
if (data === undefined || profile === undefined) {
return (
@@ -87,11 +87,13 @@ export default function ProfileFlash() {
if (!id || !build.artifactUrl) return
try {
await recordFlash({ profileId: id as Id<'profiles'> })
const url = await generateDownloadUrl({
buildId: build._id,
profileId: id as Id<'profiles'>,
})
window.location.href = url
} catch (error) {
console.error('Failed to record flash', error)
} finally {
window.open(build.artifactUrl, '_blank', 'noopener,noreferrer')
console.error('Failed to generate download URL', error)
}
}