move supabase start to its own commands

This commit is contained in:
Nate Kelley 2025-09-24 16:33:26 -06:00
parent 052901012e
commit 299ed5d697
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
21 changed files with 141 additions and 133 deletions

View File

@ -8,18 +8,18 @@
"inputs": ["server/**/*", "libs/**/*", "Cargo.toml", "Cargo.lock"]
},
"start": {
"dependsOn": ["@buster/database#start", "build"],
"dependsOn": ["@buster-app/supabase#start", "build"],
"cache": true,
"persistent": true
},
"dev": {
"dependsOn": ["@buster/database#start"],
"dependsOn": ["@buster-app/supabase#start"],
"cache": false,
"persistent": true,
"outputs": []
},
"dev:fast": {
"dependsOn": ["@buster/database#start"],
"dependsOn": ["@buster-app/supabase#start"],
"cache": false,
"persistent": true,
"outputs": []

View File

@ -3,19 +3,19 @@
"extends": ["//"],
"tasks": {
"start": {
"dependsOn": ["@buster/database#start"],
"dependsOn": ["@buster-app/supabase#start"],
"cache": true,
"persistent": false,
"outputs": []
},
"dev": {
"dependsOn": ["@buster/database#start"],
"dependsOn": ["@buster-app/supabase#start"],
"cache": false,
"persistent": true,
"outputs": []
},
"dev:fast": {
"dependsOn": ["@buster/database#start"],
"dependsOn": ["@buster-app/supabase#start"],
"cache": false,
"persistent": true,
"outputs": []

View File

@ -7,14 +7,14 @@
"outputs": ["dist/**"]
},
"start": {
"dependsOn": ["@buster/database#start", "build"],
"dependsOn": ["@buster-app/supabase#start", "build"],
"cache": true,
"persistent": true
},
"dev": {
"cache": false,
"persistent": true,
"dependsOn": ["@buster/database#start", "^build"],
"dependsOn": ["@buster-app/supabase#start", "^build"],
"with": [
"@buster/ai#dev",
"@buster/server-shared#dev",
@ -28,7 +28,7 @@
"dev:fast": {
"cache": false,
"persistent": true,
"dependsOn": ["@buster/database#start", "^build"],
"dependsOn": ["@buster-app/supabase#start", "^build"],
"with": [
"@buster/ai#dev:fast",
"@buster/server-shared#dev:fast",

View File

@ -0,0 +1 @@
main

View File

@ -3,3 +3,4 @@ volumes/storage
.env
test.http
docker-compose.override.yml
.temp

View File

@ -2,12 +2,12 @@
"name": "@buster-app/supabase",
"version": "0.0.1",
"private": false,
"dependencies": {
"@buster/database": "workspace:*"
},
"dependencies": {},
"scripts": {
"lint": "biome check . --write",
"lint:fix": "biome check . --write",
"start": "echo 'Please run this command through Turbo from the root: turbo start'"
"start": "supabase start",
"stop": "supabase stop",
"reset": "supabase stop && supabase start && supabase db reset"
}
}

View File

@ -3,7 +3,19 @@
"extends": ["//"],
"tasks": {
"start": {
"dependsOn": ["@buster/database#start"],
"dependsOn": [],
"cache": false,
"persistent": false,
"outputs": []
},
"stop": {
"dependsOn": [],
"cache": false,
"persistent": false,
"outputs": []
},
"reset": {
"dependsOn": ["stop"],
"cache": false,
"persistent": false,
"outputs": []

View File

@ -15,8 +15,8 @@
"dev": {
"cache": false,
"persistent": true,
"dependsOn": ["^build"],
"with": ["@buster-app/supabase#start"]
"dependsOn": ["^build","@buster-app/supabase#start"],
"with": ["@buster/database#dev"]
},
"dev:fast": {
"cache": false,

View File

@ -1,3 +1,4 @@
import { useMutation } from '@tanstack/react-query';
import { Link } from '@tanstack/react-router';
import type React from 'react';
import { useState } from 'react';
@ -5,32 +6,26 @@ import { Button } from '@/components/ui/buttons';
import { SuccessCard } from '@/components/ui/card/SuccessCard';
import { Input } from '@/components/ui/inputs';
import { Text, Title } from '@/components/ui/typography';
import { useBusterNotifications } from '@/context/BusterNotifications';
import { resetPasswordEmailSend } from '@/integrations/supabase/resetPassword';
import { cn } from '@/lib/classMerge';
import { isValidEmail } from '@/lib/email';
import { timeout } from '@/lib/timeout';
export const ResetEmailForm: React.FC<{
queryEmail: string;
}> = ({ queryEmail }) => {
const [loading, setLoading] = useState(false);
const [email, setEmail] = useState(queryEmail);
const [emailSent, setEmailSent] = useState(false);
const { openErrorNotification } = useBusterNotifications();
const { mutateAsync: resetPasswordEmailSendMutation, isPending: loading } = useMutation({
mutationFn: resetPasswordEmailSend,
});
const disabled = !email || !isValidEmail(email);
const handleResetPassword = async () => {
if (disabled) return;
setLoading(true);
const [res] = await Promise.all([resetPasswordEmailSend({ data: { email } }), timeout(450)]);
if (res?.error) {
openErrorNotification(res.error);
} else {
setEmailSent(true);
}
setLoading(false);
const [res] = await Promise.all([resetPasswordEmailSendMutation({ data: { email } })]);
setEmailSent(true);
};
if (emailSent) {

View File

@ -3,6 +3,7 @@ import type { User } from '@supabase/supabase-js';
import { useRouter } from '@tanstack/react-router';
import type React from 'react';
import { useCallback, useState } from 'react';
import { useGetMyUserInfo } from '@/api/buster_rest/users';
import { Button } from '@/components/ui/buttons';
import { SuccessCard } from '@/components/ui/card/SuccessCard';
import { Input } from '@/components/ui/inputs';
@ -13,8 +14,8 @@ import { PolicyCheck } from './PolicyCheck';
export const ResetPasswordForm: React.FC<{
supabaseUser: Pick<User, 'email'>;
busterUser: UserResponse;
}> = ({ supabaseUser, busterUser }) => {
}> = ({ supabaseUser }) => {
const { data: busterUser } = useGetMyUserInfo();
const router = useRouter();
const [loading, setLoading] = useState(false);
const [resetSuccess, setResetSuccess] = useState(false);

View File

@ -2,6 +2,7 @@ import { createServerFn } from '@tanstack/react-start';
import { z } from 'zod';
import { env } from '@/env';
import { ServerRoute as AuthCallbackRoute } from '../../routes/auth.callback';
import { Route as AuthResetPasswordRoute } from '../../routes/auth.reset-password';
import { getSupabaseServerClient } from './server';
export const resetPasswordEmailSend = createServerFn({ method: 'POST' })
@ -10,14 +11,17 @@ export const resetPasswordEmailSend = createServerFn({ method: 'POST' })
const supabase = await getSupabaseServerClient();
const url = env.VITE_PUBLIC_URL;
const authURLFull = `${url}${AuthCallbackRoute.to}`;
const authURLFull = `${url}${AuthResetPasswordRoute.to}`;
console.log('email', email);
console.log('authURLFull', authURLFull);
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: authURLFull,
});
if (error) {
return { error: error.message };
throw new Error(error.message);
}
return;
@ -28,6 +32,12 @@ export const resetPassword = createServerFn({ method: 'POST' })
.handler(async ({ data: { password } }) => {
const supabase = await getSupabaseServerClient();
const { data: user } = await supabase.auth.getUser();
if (!user?.user) {
throw new Error('User not found');
}
const { error } = await supabase.auth.updateUser({ password });
if (error) {

View File

@ -127,6 +127,7 @@ import { Route as AppAppAssetChatsChatIdReportsReportIdMetricsMetricIdContentCha
import { Route as AppAppAssetChatsChatIdDashboardsDashboardIdMetricsMetricIdContentSqlRouteImport } from './routes/app/_app/_asset/chats.$chatId/dashboards.$dashboardId/metrics.$metricId/_content/sql'
import { Route as AppAppAssetChatsChatIdDashboardsDashboardIdMetricsMetricIdContentResultsRouteImport } from './routes/app/_app/_asset/chats.$chatId/dashboards.$dashboardId/metrics.$metricId/_content/results'
import { Route as AppAppAssetChatsChatIdDashboardsDashboardIdMetricsMetricIdContentChartRouteImport } from './routes/app/_app/_asset/chats.$chatId/dashboards.$dashboardId/metrics.$metricId/_content/chart'
import { ServerRoute as AuthConfirmServerRouteImport } from './routes/auth.confirm'
import { ServerRoute as AuthCallbackServerRouteImport } from './routes/auth.callback'
const AppAppAssetReportsReportIdRouteImport = createFileRoute(
@ -939,6 +940,11 @@ const AppAppAssetChatsChatIdDashboardsDashboardIdMetricsMetricIdContentChartRout
AppAppAssetChatsChatIdDashboardsDashboardIdMetricsMetricIdContentRoute,
} as any,
)
const AuthConfirmServerRoute = AuthConfirmServerRouteImport.update({
id: '/auth/confirm',
path: '/auth/confirm',
getParentRoute: () => rootServerRouteImport,
} as any)
const AuthCallbackServerRoute = AuthCallbackServerRouteImport.update({
id: '/auth/callback',
path: '/auth/callback',
@ -1621,24 +1627,28 @@ export interface RootRouteChildren {
}
export interface FileServerRoutesByFullPath {
'/auth/callback': typeof AuthCallbackServerRoute
'/auth/confirm': typeof AuthConfirmServerRoute
}
export interface FileServerRoutesByTo {
'/auth/callback': typeof AuthCallbackServerRoute
'/auth/confirm': typeof AuthConfirmServerRoute
}
export interface FileServerRoutesById {
__root__: typeof rootServerRouteImport
'/auth/callback': typeof AuthCallbackServerRoute
'/auth/confirm': typeof AuthConfirmServerRoute
}
export interface FileServerRouteTypes {
fileServerRoutesByFullPath: FileServerRoutesByFullPath
fullPaths: '/auth/callback'
fullPaths: '/auth/callback' | '/auth/confirm'
fileServerRoutesByTo: FileServerRoutesByTo
to: '/auth/callback'
id: '__root__' | '/auth/callback'
to: '/auth/callback' | '/auth/confirm'
id: '__root__' | '/auth/callback' | '/auth/confirm'
fileServerRoutesById: FileServerRoutesById
}
export interface RootServerRouteChildren {
AuthCallbackServerRoute: typeof AuthCallbackServerRoute
AuthConfirmServerRoute: typeof AuthConfirmServerRoute
}
declare module '@tanstack/react-router' {
@ -2522,6 +2532,13 @@ declare module '@tanstack/react-router' {
}
declare module '@tanstack/react-start/server' {
interface ServerFileRoutesByPath {
'/auth/confirm': {
id: '/auth/confirm'
path: '/auth/confirm'
fullPath: '/auth/confirm'
preLoaderRoute: typeof AuthConfirmServerRouteImport
parentRoute: typeof rootServerRouteImport
}
'/auth/callback': {
id: '/auth/callback'
path: '/auth/callback'
@ -3309,6 +3326,7 @@ export const routeTree = rootRouteImport
._addFileTypes<FileRouteTypes>()
const rootServerRouteChildren: RootServerRouteChildren = {
AuthCallbackServerRoute: AuthCallbackServerRoute,
AuthConfirmServerRoute: AuthConfirmServerRoute,
}
export const serverRouteTree = rootServerRouteImport
._addFileChildren(rootServerRouteChildren)

View File

@ -0,0 +1,54 @@
import type { EmailOtpType } from '@supabase/supabase-js';
import { redirect } from '@tanstack/react-router';
import { createServerFileRoute } from '@tanstack/react-start/server';
import { z } from 'zod';
import { getSupabaseServerClient } from '@/integrations/supabase/server';
import { Route as AuthResetPasswordRoute } from './auth.reset-password';
const searchParamsSchema = z.object({
code: z.string().optional(),
token_hash: z.string().optional(),
next: z.string().optional(),
type: z.string().optional(),
});
export const ServerRoute = createServerFileRoute('/auth/confirm').methods({
GET: async ({ request }) => {
const url = new URL(request.url);
const { data: searchParams } = searchParamsSchema.safeParse({
code: url.searchParams.get('code') || undefined,
token_hash: url.searchParams.get('token_hash') || undefined,
type: url.searchParams.get('type') || undefined,
next: url.searchParams.get('next') || undefined,
});
if (!searchParams) {
return new Response('Invalid search params', { status: 400 });
}
const supabase = await getSupabaseServerClient();
const { token_hash, type } = searchParams;
if (!token_hash || !type) {
return new Response('Invalid search params', { status: 400 });
}
const { data, error } = await supabase.auth.verifyOtp({
token_hash: token_hash,
type: type as EmailOtpType,
});
if (!error) {
throw redirect({
to: AuthResetPasswordRoute.to,
search: {
email: data.user?.email || '',
},
});
}
console.error('Error verifying OTP', error);
return new Response('Error verifying OTP', { status: 400 });
},
});

View File

@ -1,17 +1,10 @@
import { createFileRoute } from '@tanstack/react-router';
import { z } from 'zod';
import { prefetchGetMyUserInfo } from '@/api/buster_rest/users';
import { ResetEmailForm } from '@/components/features/auth/ResetEmailForm';
import { ResetPasswordForm } from '@/components/features/auth/ResetPasswordForm';
import { useGetSupabaseUser } from '@/context/Supabase';
export const Route = createFileRoute('/auth/reset-password')({
loader: async ({ context }) => {
const user = await prefetchGetMyUserInfo(context.queryClient);
return {
user,
};
},
head: () => ({
meta: [
{ title: 'Reset Password' },
@ -22,26 +15,17 @@ export const Route = createFileRoute('/auth/reset-password')({
}),
component: RouteComponent,
validateSearch: z.object({
email: z.string(),
email: z.string().optional(),
}),
});
function RouteComponent() {
const { user } = Route.useLoaderData();
const { email } = Route.useSearch();
const supabaseUser = useGetSupabaseUser();
if (email) {
return <ResetEmailForm queryEmail={email} />;
if (email || supabaseUser?.is_anonymous || !supabaseUser) {
return <ResetEmailForm queryEmail={email || ''} />;
}
if (!supabaseUser || !user) {
return (
<div className="flex h-full flex-col items-center justify-center p-10">
We were unable to find your account
</div>
);
}
return <ResetPasswordForm supabaseUser={supabaseUser} busterUser={user} />;
return <ResetPasswordForm supabaseUser={supabaseUser} />;
}

View File

@ -31,15 +31,12 @@
"build": "tsc",
"build:dry-run": "tsc",
"db:init": "echo 'Initializing database...'",
"start": "supabase start",
"db:reset": "supabase stop && supabase start && supabase db reset",
"db:seed": "tsx scripts/seed.ts",
"db:dump": "tsx scripts/dump.ts",
"db:migrate": "drizzle-kit migrate",
"db:generate": "drizzle-kit generate",
"db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio",
"db:stop": "supabase stop",
"dev": "tsc --watch",
"lint": "biome check --write",
"test": "vitest run",
@ -53,6 +50,7 @@
"@buster/env-utils": "workspace:*",
"@buster/typescript-config": "workspace:*",
"@buster/vitest-config": "workspace:*",
"@buster-app/supabase": "workspace:*",
"ai": "catalog:",
"drizzle-kit": "^0.31.4",
"drizzle-orm": "catalog:",

View File

@ -1,48 +0,0 @@
#!/usr/bin/env tsx
import { exec } from 'node:child_process';
import { promisify } from 'node:util';
const execAsync = promisify(exec);
async function runCommand(command: string): Promise<void> {
console.log(`🔄 Running: ${command}`);
try {
const { stdout, stderr } = await execAsync(command);
if (stdout) {
console.log(stdout);
}
if (stderr) {
console.error(stderr);
}
console.log(`✅ Successfully completed: ${command}\n`);
} catch (error) {
console.error(`❌ Error running command: ${command}`);
console.error(error);
throw error;
}
}
async function startSupabase(): Promise<void> {
console.log('🚀 Starting Supabase setup...\n');
try {
// Start Supabase
await runCommand('npm run db:start-supabase');
// Reset the database
await runCommand('npm run db:reset');
console.log('🎉 Supabase setup completed successfully!');
} catch (error) {
console.error('💥 Supabase setup failed:', error);
process.exit(1);
}
}
// Run the script
startSupabase();

View File

@ -1,4 +0,0 @@
# Supabase
.branches
.temp
.env

View File

@ -9,25 +9,17 @@
"db:init": {
"cache": false,
"persistent": false,
"dependsOn": ["db:seed"]
},
"start": {
"cache": false,
"persistent": false,
"outputs": []
},
"db:reset": {
"cache": false,
"persistent": false
"dependsOn": ["db:seed", "@buster-app/supabase#start"]
},
"db:migrate": {
"cache": false,
"persistent": false
"persistent": false,
"dependsOn": ["@buster-app/supabase#start"]
},
"db:seed": {
"cache": false,
"persistent": false,
"dependsOn": ["db:migrate"]
"dependsOn": ["db:migrate", "@buster-app/supabase#start"]
},
"db:dump": {
"cache": false,
@ -45,14 +37,9 @@
"cache": false,
"persistent": true
},
"db:stop": {
"cache": false,
"persistent": false
},
"dev": {
"cache": false,
"persistent": true,
"dependsOn": ["start"]
"persistent": true
}
}
}

View File

@ -329,11 +329,7 @@ importers:
specifier: ^4.17.12
version: 4.17.12
apps/supabase:
dependencies:
'@buster/database':
specifier: workspace:*
version: link:../../packages/database
apps/supabase: {}
apps/trigger:
dependencies:
@ -1159,6 +1155,9 @@ importers:
packages/database:
dependencies:
'@buster-app/supabase':
specifier: workspace:*
version: link:../../apps/supabase
'@buster/env-utils':
specifier: workspace:*
version: link:../env-utils