bug rls (not solved)

This commit is contained in:
vergiLgood1 2025-03-05 22:51:46 +07:00
parent b2784cc108
commit bc03c18a05
6 changed files with 166 additions and 40 deletions

View File

@ -1,10 +1,26 @@
export default function DashboardPage() { import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";
export default async function DashboardPage() {
const supabase = await createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) {
return redirect("/sign-in");
}
return ( return (
<> <>
<header className="flex h-12 shrink-0 items-center justify-end border-b px-4 mb-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-8"></header> <header className="flex h-12 shrink-0 items-center justify-end border-b px-4 mb-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-8"></header>
<div className="flex flex-1 flex-col gap-4 p-4 pt-0"> <div className="flex flex-1 flex-col gap-4 p-4 pt-0">
<div className="grid auto-rows-min gap-4 md:grid-cols-3"> <div className="grid auto-rows-min gap-4 md:grid-cols-3">
<div className="aspect-video rounded-xl bg-muted/50" /> <div className="aspect-video rounded-xl bg-muted/50">
<pre className="text-xs font-mono p-3 rounded border overflow-auto">
{JSON.stringify(user, null, 2)}
</pre>
</div>
<div className="aspect-video rounded-xl bg-muted/50" /> <div className="aspect-video rounded-xl bg-muted/50" />
<div className="aspect-video rounded-xl bg-muted/50" /> <div className="aspect-video rounded-xl bg-muted/50" />
</div> </div>

View File

@ -9,6 +9,7 @@ import {
UserResponse, UserResponse,
} from "@/src/models/users/users.model"; } from "@/src/models/users/users.model";
import { createClient } from "@/utils/supabase/server"; import { createClient } from "@/utils/supabase/server";
import { createClient as createClientSide } from "@/utils/supabase/client";
import { createAdminClient } from "@/utils/supabase/admin"; import { createAdminClient } from "@/utils/supabase/admin";
// Initialize Supabase client with admin key // Initialize Supabase client with admin key
@ -100,6 +101,56 @@ export async function createUser(
}; };
} }
export async function uploadAvatar(userId: string, email: string, file: File) {
try {
const supabase = createClientSide();
// Pastikan mendapatkan session untuk autentikasi
const { data: session } = await supabase.auth.getSession();
if (!session) throw new Error("User is not authenticated");
const baseUrl = `${process.env.NEXT_PUBLIC_SUPABASE_STORAGE_URL}/avatars`;
const fileExt = file.name.split(".").pop();
const emailName = email.split("@")[0];
const fileName = `AVR-${emailName}.${fileExt}`;
const filePath = `${baseUrl}/${fileName}`;
const { error: uploadError } = await supabase.storage
.from("avatars")
.upload(fileName, file, {
upsert: false,
contentType: file.type,
});
if (uploadError) {
throw uploadError;
}
await db.users.update({
where: {
id: userId,
},
data: {
profile: {
update: {
avatar: filePath,
},
},
},
});
const {
data: { publicUrl },
} = supabase.storage.from("avatars").getPublicUrl(fileName);
return publicUrl;
} catch (error) {
console.error("Error uploading avatar:", error);
throw error;
}
}
// Update an existing user // Update an existing user
export async function updateUser( export async function updateUser(
userId: string, userId: string,
@ -110,7 +161,8 @@ export async function updateUser(
const { data, error } = await supabase.auth.admin.updateUserById(userId, { const { data, error } = await supabase.auth.admin.updateUserById(userId, {
email: params.email, email: params.email,
phone: params.phone, phone: params.phone,
password: params.password, password_hash: params.password_hash,
ban_duration: params.ban_duration,
}); });
if (error) { if (error) {
@ -118,9 +170,52 @@ export async function updateUser(
throw new Error(error.message); throw new Error(error.message);
} }
const user = await db.users.findUnique({
where: {
id: userId,
},
include: {
profile: true,
},
});
if (!user) {
throw new Error("User not found");
}
const updateUser = await db.users.update({
where: {
id: userId,
},
data: {
role: params.role,
profile: {
update: {
avatar: params.profile.avatar || user.profile?.avatar,
username: params.profile.username || user.profile?.username,
first_name: params.profile.first_name || user.profile?.first_name,
last_name: params.profile.last_name || user.profile?.last_name,
bio: params.profile.bio || user.profile?.bio,
address: params.profile.address,
birth_date: params.profile.birth_date || user.profile?.birth_date,
},
},
},
include: {
profile: true,
},
});
return { return {
data: { data: {
user: data.user, user: {
...data.user,
role: params.role,
profile: {
user_id: userId,
...updateUser.profile,
},
},
}, },
error: null, error: null,
}; };

View File

@ -27,11 +27,14 @@ import { Label } from "@/app/_components/ui/label";
import { Separator } from "@/app/_components/ui/separator"; import { Separator } from "@/app/_components/ui/separator";
import { Switch } from "@/app/_components/ui/switch"; import { Switch } from "@/app/_components/ui/switch";
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import { createClient } from "@/utils/supabase/client";
import { ScrollArea } from "@/app/_components/ui/scroll-area"; import { ScrollArea } from "@/app/_components/ui/scroll-area";
import {
updateUser,
uploadAvatar,
} from "@/app/(protected)/(admin)/dashboard/user-management/action";
const profileFormSchema = z.object({ const profileFormSchema = z.object({
preferred_name: z.string().nullable().optional(), username: z.string().nullable().optional(),
avatar: z.string().nullable().optional(), avatar: z.string().nullable().optional(),
}); });
@ -44,46 +47,32 @@ interface ProfileSettingsProps {
export function ProfileSettings({ user }: ProfileSettingsProps) { export function ProfileSettings({ user }: ProfileSettingsProps) {
const [isUploading, setIsUploading] = useState(false); const [isUploading, setIsUploading] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
const supabase = createClient();
// Use profile data with fallbacks // Use profile data with fallbacks
const preferredName = user?.profile?.first_name || ""; const username = user?.profile?.username || "";
const userEmail = user?.email || ""; const userEmail = user?.email || "";
const userAvatar = user?.profile?.avatar || ""; const userAvatar = user?.profile?.avatar || "";
const form = useForm<ProfileFormValues>({ const form = useForm<ProfileFormValues>({
resolver: zodResolver(profileFormSchema), resolver: zodResolver(profileFormSchema),
defaultValues: { defaultValues: {
preferred_name: preferredName || "", username: username || "",
avatar: userAvatar || "", avatar: userAvatar || "",
}, },
}); });
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => { const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]; const file = e.target.files?.[0];
if (!file || !user?.id) return;
if (!file || !user?.id || !user?.email) return;
try { try {
setIsUploading(true); setIsUploading(true);
// Upload to Supabase Storage // Upload avatar to storage
const fileExt = file.name.split(".").pop(); const publicUrl = await uploadAvatar(user.id, user.email, file);
const fileName = `${user.id}-${Date.now()}.${fileExt}`;
const filePath = `avatars/${fileName}`;
const { error: uploadError, data } = await supabase.storage console.log("publicUrl", publicUrl);
.from("profiles")
.upload(filePath, file, {
upsert: true,
contentType: file.type,
});
if (uploadError) throw uploadError;
// Get the public URL
const {
data: { publicUrl },
} = supabase.storage.from("profiles").getPublicUrl(filePath);
// Update the form value // Update the form value
form.setValue("avatar", publicUrl); form.setValue("avatar", publicUrl);
@ -103,13 +92,12 @@ export function ProfileSettings({ user }: ProfileSettingsProps) {
if (!user?.id) return; if (!user?.id) return;
// Update profile in database // Update profile in database
const { error } = await supabase const { error } = await updateUser(user.id, {
.from("profiles") profile: {
.update({ avatar: data.avatar || undefined,
first_name: data.preferred_name, username: data.username || undefined,
avatar: data.avatar, },
}) });
.eq("user_id", user.id);
if (error) throw error; if (error) throw error;
} catch (error) { } catch (error) {
@ -133,10 +121,10 @@ export function ProfileSettings({ user }: ProfileSettingsProps) {
<Avatar className="h-16 w-16"> <Avatar className="h-16 w-16">
<AvatarImage <AvatarImage
src={form.watch("avatar") || ""} src={form.watch("avatar") || ""}
alt={preferredName} alt={username}
/> />
<AvatarFallback> <AvatarFallback>
{preferredName?.[0]?.toUpperCase() || {username?.[0]?.toUpperCase() ||
userEmail?.[0]?.toUpperCase()} userEmail?.[0]?.toUpperCase()}
</AvatarFallback> </AvatarFallback>
<div className="absolute inset-0 flex items-center justify-center bg-black/50 rounded-full opacity-0 group-hover:opacity-100 transition-opacity"> <div className="absolute inset-0 flex items-center justify-center bg-black/50 rounded-full opacity-0 group-hover:opacity-100 transition-opacity">
@ -160,7 +148,7 @@ export function ProfileSettings({ user }: ProfileSettingsProps) {
<Label>Preferred name</Label> <Label>Preferred name</Label>
<FormField <FormField
control={form.control} control={form.control}
name="preferred_name" name="username"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormControl> <FormControl>

View File

@ -3,10 +3,22 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
images: { images: {
remotePatterns: [ remotePatterns: [
{
protocol: "https",
hostname: "images.unsplash.com",
},
{ {
protocol: "https", protocol: "https",
hostname: "images.pexels.com", hostname: "images.pexels.com",
}, },
{
protocol: "https",
hostname: "images.unsplash.com",
},
{
protocol: "https",
hostname: "cppejroeyonsqxulinaj.supabase.co",
},
], ],
}, },
}; };

View File

@ -34,11 +34,13 @@ const timestampSchema = z.union([z.string(), z.date()]).nullable();
// email: z.string().email(), // email: z.string().email(),
// }); // });
export const UserSchema = z.object({ export const UserSchema = z.object({
id: z.string(), id: z.string(),
role: z.string().optional(), role: z.string().optional(),
email: z.string().email().optional(), email: z.string().email().optional(),
email_confirmed_at: z.union([z.string(), z.date()]).nullable().optional(), email_confirmed_at: z.union([z.string(), z.date()]).nullable().optional(),
password_hash: z.string().nullable().optional(),
invited_at: z.union([z.string(), z.date()]).nullable().optional(), invited_at: z.union([z.string(), z.date()]).nullable().optional(),
phone: z.string().nullable().optional(), phone: z.string().nullable().optional(),
confirmed_at: z.union([z.string(), z.date()]).nullable().optional(), confirmed_at: z.union([z.string(), z.date()]).nullable().optional(),
@ -50,9 +52,10 @@ export const UserSchema = z.object({
banned_until: z.union([z.string(), z.date()]).nullable().optional(), banned_until: z.union([z.string(), z.date()]).nullable().optional(),
profile: z profile: z
.object({ .object({
id: z.string(), id: z.string().optional(),
user_id: z.string(), user_id: z.string(),
avatar: z.string().nullable().optional(), avatar: z.string().nullable().optional(),
username: z.string().nullable().optional(),
first_name: z.string().nullable().optional(), first_name: z.string().nullable().optional(),
last_name: z.string().nullable().optional(), last_name: z.string().nullable().optional(),
bio: z.string().nullable().optional(), bio: z.string().nullable().optional(),
@ -69,11 +72,12 @@ export const ProfileSchema = z.object({
id: z.string(), id: z.string(),
user_id: z.string(), user_id: z.string(),
avatar: z.string().optional(), avatar: z.string().optional(),
username: z.string().optional(),
first_name: z.string().optional(), first_name: z.string().optional(),
last_name: z.string().optional(), last_name: z.string().optional(),
bio: z.string(), bio: z.string(),
address: z.string().optional(), address: z.string().optional(),
birthdate: z.string().optional(), birth_date: z.string().optional(),
}); });
export type Profile = z.infer<typeof ProfileSchema>; export type Profile = z.infer<typeof ProfileSchema>;
@ -91,7 +95,18 @@ export type CreateUserParams = z.infer<typeof CreateUserParamsSchema>;
export const UpdateUserParamsSchema = z.object({ export const UpdateUserParamsSchema = z.object({
email: z.string().email().optional(), email: z.string().email().optional(),
phone: z.string().optional(), phone: z.string().optional(),
password: z.string().optional(), password_hash: z.string().optional(),
ban_duration: z.string().optional(),
role: z.enum(["user", "staff", "admin"]).optional(),
profile: z.object({
avatar: z.string().optional(),
username: z.string().optional(),
first_name: z.string().optional(),
last_name: z.string().optional(),
bio: z.string().optional(),
address: z.string().optional(),
birth_date: z.string().optional(),
}),
}); });
export type UpdateUserParams = z.infer<typeof UpdateUserParamsSchema>; export type UpdateUserParams = z.infer<typeof UpdateUserParamsSchema>;