Add some actions to make stuff easy
This commit is contained in:
149
src/lib/actions/auth.ts
Normal file
149
src/lib/actions/auth.ts
Normal file
@ -0,0 +1,149 @@
|
||||
'use server';
|
||||
|
||||
import 'server-only';
|
||||
import { encodedRedirect } from '@/utils/utils';
|
||||
import { createServerClient } from '@/utils/supabase';
|
||||
import { headers } from 'next/headers';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export const signUp = async (formData: FormData) => {
|
||||
const email = formData.get('email') as string;
|
||||
const password = formData.get('password') as string;
|
||||
const supabase = await createServerClient();
|
||||
const origin = (await headers()).get('origin');
|
||||
|
||||
if (!email || !password) {
|
||||
return encodedRedirect(
|
||||
'error',
|
||||
'/sign-up',
|
||||
'Email & password are required',
|
||||
);
|
||||
}
|
||||
|
||||
const { error } = await supabase.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
options: {
|
||||
emailRedirectTo: `${origin}/auth/callback`,
|
||||
},
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error(error.code + ': ' + error.message);
|
||||
return encodedRedirect(
|
||||
'error',
|
||||
'/sign-up',
|
||||
'Thanks for signing up! Please check your email for a verification link.',
|
||||
);
|
||||
} else {
|
||||
return encodedRedirect(
|
||||
'success',
|
||||
'/sign-up',
|
||||
'Thanks for signing up! Please check your email for a verification link.',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const signIn = async (formData: FormData) => {
|
||||
const email = formData.get('email') as string;
|
||||
const password = formData.get('password') as string;
|
||||
const supabase = await createServerClient();
|
||||
|
||||
const { error } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return encodedRedirect('error', '/sign-in', error.message);
|
||||
}
|
||||
|
||||
return redirect('/protected');
|
||||
};
|
||||
|
||||
export const forgotPassword = async (formData: FormData) => {
|
||||
const email = formData.get('email') as string;
|
||||
const supabase = await createServerClient();
|
||||
const origin = (await headers()).get('origin');
|
||||
const callbackUrl = formData.get('callbackUrl') as string;
|
||||
|
||||
if (!email) {
|
||||
return encodedRedirect('error', '/forgot-password', 'Email is required');
|
||||
}
|
||||
|
||||
const { error } = await supabase.auth.resetPasswordForEmail(email, {
|
||||
redirectTo: `${origin}/auth/callback?redirect_to=/protected/reset-password`,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error(error.message);
|
||||
return encodedRedirect(
|
||||
'error',
|
||||
'/forgot-password',
|
||||
'Could not reset password',
|
||||
);
|
||||
}
|
||||
|
||||
if (callbackUrl) {
|
||||
return redirect(callbackUrl);
|
||||
}
|
||||
|
||||
return encodedRedirect(
|
||||
'success',
|
||||
'/forgot-password',
|
||||
'Check your email for a link to reset your password.',
|
||||
);
|
||||
};
|
||||
|
||||
export const resetPassword = async (formData: FormData) => {
|
||||
const supabase = await createServerClient();
|
||||
const password = formData.get('password') as string;
|
||||
const confirmPassword = formData.get('confirmPassword') as string;
|
||||
|
||||
if (!password || !confirmPassword) {
|
||||
encodedRedirect(
|
||||
'error',
|
||||
'/protected/reset-password',
|
||||
'Password and confirm password are required',
|
||||
);
|
||||
}
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
encodedRedirect(
|
||||
'error',
|
||||
'/protected/reset-password',
|
||||
'Passwords do not match',
|
||||
);
|
||||
}
|
||||
|
||||
const { error } = await supabase.auth.updateUser({
|
||||
password: password,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
encodedRedirect(
|
||||
'error',
|
||||
'/protected/reset-password',
|
||||
'Password update failed',
|
||||
);
|
||||
}
|
||||
|
||||
encodedRedirect('success', '/protected/reset-password', 'Password updated');
|
||||
};
|
||||
|
||||
export const signOut = async () => {
|
||||
const supabase = await createServerClient();
|
||||
await supabase.auth.signOut();
|
||||
return redirect('/sign-in');
|
||||
};
|
||||
|
||||
export const getUser = async () => {
|
||||
try {
|
||||
const supabase = await createServerClient();
|
||||
const { data, error } = await supabase.auth.getUser();
|
||||
if (error) throw error;
|
||||
return data.user;
|
||||
} catch (error) {
|
||||
console.error('Could not get user!', error);
|
||||
}
|
||||
};
|
7
src/lib/actions/index.ts
Normal file
7
src/lib/actions/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export * from './auth';
|
||||
export * from './storage';
|
||||
export * from './public';
|
||||
|
||||
export type Result<T> =
|
||||
| { success: true; data: T }
|
||||
| { success: false; error: string };
|
30
src/lib/actions/public.ts
Normal file
30
src/lib/actions/public.ts
Normal file
@ -0,0 +1,30 @@
|
||||
'use server';
|
||||
|
||||
import 'server-only';
|
||||
import { createServerClient, type Profile } from '@/utils/supabase';
|
||||
import { getUser } from '@/lib/actions';
|
||||
import type { Result } from './index';
|
||||
|
||||
export const getProfile = async (): Promise<Result<Profile>> => {
|
||||
try {
|
||||
const user = await getUser();
|
||||
if (!user) throw new Error('User not found');
|
||||
const supabase = await createServerClient();
|
||||
const { data, error } = await supabase
|
||||
.from('profiles')
|
||||
.select('*')
|
||||
.eq('id', user.id)
|
||||
.single();
|
||||
if (error) throw error;
|
||||
return { success: true, data: data as Profile };
|
||||
} catch (error) {
|
||||
console.error('Could not get profile!', error);
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'Unknown error getting profile',
|
||||
};
|
||||
}
|
||||
};
|
184
src/lib/actions/storage.ts
Normal file
184
src/lib/actions/storage.ts
Normal file
@ -0,0 +1,184 @@
|
||||
'use server';
|
||||
import 'server-only';
|
||||
import { createServerClient } from '@/utils/supabase';
|
||||
import type { Result } from './index';
|
||||
|
||||
export type GetStorageProps = {
|
||||
bucket: string;
|
||||
url: string;
|
||||
seconds?: number;
|
||||
transform?: {
|
||||
width?: number;
|
||||
height?: number;
|
||||
quality?: number;
|
||||
format?: 'origin';
|
||||
resize?: 'cover' | 'contain' | 'fill';
|
||||
};
|
||||
download?: boolean;
|
||||
};
|
||||
|
||||
export type UploadStorageProps = {
|
||||
bucket: string;
|
||||
path: string;
|
||||
file: File;
|
||||
options?: {
|
||||
cacheControl?: string;
|
||||
upsert?: boolean;
|
||||
contentType?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export async function getSignedUrl({
|
||||
bucket,
|
||||
url,
|
||||
seconds = 3600,
|
||||
transform,
|
||||
download = false,
|
||||
}: GetStorageProps): Promise<Result<string>> {
|
||||
try {
|
||||
const supabase = await createServerClient();
|
||||
const { data, error } = await supabase.storage
|
||||
.from(bucket)
|
||||
.createSignedUrl(url, seconds, { transform });
|
||||
|
||||
if (error) throw error;
|
||||
if (!data?.signedUrl) throw new Error('No signed URL returned');
|
||||
|
||||
// Safely add download parameter if needed
|
||||
if (download) {
|
||||
const urlObj = new URL(data.signedUrl);
|
||||
urlObj.searchParams.append('download', '');
|
||||
return { success: true, data: urlObj.toString() };
|
||||
}
|
||||
|
||||
return { success: true, data: data.signedUrl };
|
||||
} catch (error) {
|
||||
console.error('Could not get signed URL for asset!', error);
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'Unknown error getting signed URL',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPublicUrl({
|
||||
bucket,
|
||||
url,
|
||||
transform = {},
|
||||
download = false,
|
||||
}: GetStorageProps): Promise<Result<string>> {
|
||||
try {
|
||||
const supabase = await createServerClient();
|
||||
const { data } = supabase.storage
|
||||
.from(bucket)
|
||||
.getPublicUrl(url, { transform });
|
||||
|
||||
if (!data?.publicUrl) throw new Error('No public URL returned');
|
||||
|
||||
// Safely add download parameter if needed
|
||||
if (download) {
|
||||
const urlObj = new URL(data.publicUrl);
|
||||
urlObj.searchParams.append('download', '');
|
||||
return { success: true, data: urlObj.toString() };
|
||||
}
|
||||
|
||||
return { success: true, data: data.publicUrl };
|
||||
} catch (error) {
|
||||
console.error('Could not get public URL for asset!', error);
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'Unknown error getting public URL',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function uploadFile({
|
||||
bucket,
|
||||
path,
|
||||
file,
|
||||
options = {},
|
||||
}: UploadStorageProps): Promise<Result<string>> {
|
||||
try {
|
||||
const supabase = await createServerClient();
|
||||
const { data, error } = await supabase.storage
|
||||
.from(bucket)
|
||||
.upload(path, file, options);
|
||||
|
||||
if (error) throw error;
|
||||
if (!data?.path) throw new Error('No path returned from upload');
|
||||
|
||||
return { success: true, data: data.path };
|
||||
} catch (error) {
|
||||
console.error('Could not upload file!', error);
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
error instanceof Error ? error.message : 'Unknown error uploading file',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Add a helper to delete files
|
||||
export async function deleteFile({
|
||||
bucket,
|
||||
path,
|
||||
}: {
|
||||
bucket: string;
|
||||
path: string[];
|
||||
}): Promise<Result<null>> {
|
||||
try {
|
||||
const supabase = await createServerClient();
|
||||
const { error } = await supabase.storage.from(bucket).remove(path);
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
return { success: true, data: null };
|
||||
} catch (error) {
|
||||
console.error('Could not delete file!', error);
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
error instanceof Error ? error.message : 'Unknown error deleting file',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Add a helper to list files in a bucket
|
||||
export async function listFiles({
|
||||
bucket,
|
||||
path = '',
|
||||
options = {},
|
||||
}: {
|
||||
bucket: string;
|
||||
path?: string;
|
||||
options?: {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
sortBy?: { column: string; order: 'asc' | 'desc' };
|
||||
};
|
||||
}): Promise<Result<Array<{ name: string; id: string; metadata: any }>>> {
|
||||
try {
|
||||
const supabase = await createServerClient();
|
||||
const { data, error } = await supabase.storage
|
||||
.from(bucket)
|
||||
.list(path, options);
|
||||
|
||||
if (error) throw error;
|
||||
if (!data) throw new Error('No data returned from list operation');
|
||||
|
||||
return { success: true, data };
|
||||
} catch (error) {
|
||||
console.error('Could not list files!', error);
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
error instanceof Error ? error.message : 'Unknown error listing files',
|
||||
};
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user