Add some actions to make stuff easy

This commit is contained in:
2025-05-14 15:37:03 -05:00
parent 0cf1049ec6
commit c5eed77822
13 changed files with 271 additions and 75 deletions

View File

@ -1,4 +0,0 @@
'use server';
import 'server-only';
import { createServerClient } from '@/utils/supabase';

View File

@ -1,5 +1,5 @@
import Link from 'next/link';
import { forgotPassword } from '@/actions/auth';
import { forgotPassword } from '@/lib/actions';
import { FormMessage, type Message, SubmitButton } from '@/components/default';
import { Input, Label } from '@/components/ui';
import { SmtpMessage } from '@/app/(auth-pages)/smtp-message';

View File

@ -1,5 +1,5 @@
import Link from 'next/link';
import { signIn } from '@/actions/auth';
import { signIn } from '@/lib/actions';
import { FormMessage, type Message, SubmitButton } from '@/components/default';
import { Input, Label } from '@/components/ui';

View File

@ -1,5 +1,5 @@
import Link from 'next/link';
import { signUp } from '@/actions/auth';
import { signUp } from '@/lib/actions';
import { FormMessage, type Message, SubmitButton } from '@/components/default';
import { Input, Label } from '@/components/ui';
import { SmtpMessage } from '@/app/(auth-pages)/smtp-message';

View File

@ -1,4 +1,4 @@
import { resetPassword } from '@/actions/auth';
import { resetPassword } from '@/lib/actions';
import { FormMessage, type Message, SubmitButton } from '@/components/default';
import { Input, Label } from '@/components/ui';

View File

@ -1,54 +1,21 @@
'use server';
import { createServerClient } from '@/utils/supabase';
import { getSignedUrl, getProfile } from '@/lib/actions';
import Image from 'next/image';
export default async function Page() {
const supabase = await createServerClient();
const Page = async () => {
const user = await getProfile();
if (!user.success) throw new Error(user.error);
// Get authenticated user
const {
data: { user: authUser },
error: userError,
} = await supabase.auth.getUser();
if (userError || !authUser) {
return (
<div>
Error loading user: {userError?.message ?? 'User not authenticated'}
</div>
);
}
// Get user profile
const { data: user, error: profileError } = await supabase
.from('profiles')
.select('*')
.eq('id', authUser.id)
.single();
if (profileError || !user) {
return (
<div>
Error loading profile: {profileError?.message ?? 'Profile not found'}
</div>
);
}
// Check if avatar URL exists
if (!user.avatar_url) {
return <div>No avatar image available</div>;
}
// Get public URL for the avatar
const { data: imageData } = await supabase.storage
.from('avatars')
.createSignedUrl(user.avatar_url, 3600);
const imageUrl = await getSignedUrl({
bucket: 'avatars',
url: user.data.avatar_url ?? '',
});
if (!imageUrl.success) throw new Error(imageUrl.error);
return (
<div className='flex justify-center items-center p-8'>
<Image
src={imageData?.signedUrl ?? '/images/default_pfp.png'}
src={imageUrl.data ?? '/favicon.ico'}
alt='User avatar'
width={150}
height={150}
@ -57,4 +24,5 @@ export default async function Page() {
/>
</div>
);
}
};
export default Page;

View File

@ -3,7 +3,7 @@
import Link from 'next/link';
import { Button } from '@/components/ui';
import { createServerClient } from '@/utils/supabase';
import { signOut } from '@/actions/auth';
import { signOut } from '@/lib/actions';
const NavigationAuth = async () => {
const supabase = await createServerClient();

View File

@ -136,3 +136,14 @@ export const signOut = async () => {
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
View 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
View 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
View 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',
};
}
}