add trigger for auth.users to custom public.user
This commit is contained in:
parent
39a0af5e0f
commit
10ce404a1d
|
@ -1,3 +1,5 @@
|
|||
{
|
||||
"recommendations": ["denoland.vscode-deno"]
|
||||
"recommendations": [
|
||||
"denoland.vscode-deno"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
{
|
||||
"deno.enablePaths": ["supabase/functions"],
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "denoland.vscode-deno"
|
||||
},
|
||||
"deno.enablePaths": [
|
||||
"supabase/functions"
|
||||
],
|
||||
"deno.lint": true,
|
||||
"deno.unstable": [
|
||||
"bare-node-builtins",
|
||||
|
@ -15,8 +20,5 @@
|
|||
"fs",
|
||||
"http",
|
||||
"net"
|
||||
],
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react"
|
||||
import { DayPicker } from "react-day-picker"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { buttonVariants } from "@/app/_components/ui/button"
|
||||
|
||||
export type CalendarProps = React.ComponentProps<typeof DayPicker>
|
||||
|
||||
function Calendar({
|
||||
className,
|
||||
classNames,
|
||||
showOutsideDays = true,
|
||||
...props
|
||||
}: CalendarProps) {
|
||||
return (
|
||||
<DayPicker
|
||||
showOutsideDays={showOutsideDays}
|
||||
className={cn("p-3", className)}
|
||||
classNames={{
|
||||
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
|
||||
month: "space-y-4",
|
||||
caption: "flex justify-center pt-1 relative items-center",
|
||||
caption_label: "text-sm font-medium",
|
||||
nav: "space-x-1 flex items-center",
|
||||
nav_button: cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
|
||||
),
|
||||
nav_button_previous: "absolute left-1",
|
||||
nav_button_next: "absolute right-1",
|
||||
table: "w-full border-collapse space-y-1",
|
||||
head_row: "flex",
|
||||
head_cell:
|
||||
"text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]",
|
||||
row: "flex w-full mt-2",
|
||||
cell: cn(
|
||||
"relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected].day-range-end)]:rounded-r-md",
|
||||
props.mode === "range"
|
||||
? "[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
|
||||
: "[&:has([aria-selected])]:rounded-md"
|
||||
),
|
||||
day: cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
"h-8 w-8 p-0 font-normal aria-selected:opacity-100"
|
||||
),
|
||||
day_range_start: "day-range-start",
|
||||
day_range_end: "day-range-end",
|
||||
day_selected:
|
||||
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
|
||||
day_today: "bg-accent text-accent-foreground",
|
||||
day_outside:
|
||||
"day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
|
||||
day_disabled: "text-muted-foreground opacity-50",
|
||||
day_range_middle:
|
||||
"aria-selected:bg-accent aria-selected:text-accent-foreground",
|
||||
day_hidden: "invisible",
|
||||
...classNames,
|
||||
}}
|
||||
components={{
|
||||
IconLeft: ({ className, ...props }) => (
|
||||
<ChevronLeft className={cn("h-4 w-4", className)} {...props} />
|
||||
),
|
||||
IconRight: ({ className, ...props }) => (
|
||||
<ChevronRight className={cn("h-4 w-4", className)} {...props} />
|
||||
),
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
Calendar.displayName = "Calendar"
|
||||
|
||||
export { Calendar }
|
|
@ -0,0 +1,55 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Tabs = TabsPrimitive.Root
|
||||
|
||||
const TabsList = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsList.displayName = TabsPrimitive.List.displayName
|
||||
|
||||
const TabsTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
||||
|
||||
const TabsContent = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsContent.displayName = TabsPrimitive.Content.displayName
|
||||
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
|
@ -15,7 +15,7 @@ import {
|
|||
SidebarRail,
|
||||
} from "@/app/_components/ui/sidebar";
|
||||
import { NavPreMain } from "./navigations/nav-pre-main";
|
||||
import { navData } from "@/data/nav";
|
||||
import { navData } from "@/prisma/data/nav";
|
||||
|
||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
return (
|
||||
|
|
|
@ -0,0 +1,235 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/app/_components/ui/button";
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetFooter,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
} from "@/app/_components/ui/sheet";
|
||||
import { Input } from "@/app/_components/ui/input";
|
||||
import { Label } from "@/app/_components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/app/_components/ui/select";
|
||||
import { User } from "./users-table";
|
||||
|
||||
interface AddUserSheetProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onSave: (
|
||||
userData: Omit<User, "id" | "lastSignedIn"> & { password: string }
|
||||
) => void;
|
||||
}
|
||||
|
||||
export function AddUserSheet({
|
||||
open,
|
||||
onOpenChange,
|
||||
onSave,
|
||||
}: AddUserSheetProps) {
|
||||
const [userData, setUserData] = useState<
|
||||
Omit<User, "id" | "lastSignedIn"> & { password: string }
|
||||
>({
|
||||
email: "",
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
role: "user",
|
||||
status: "active",
|
||||
password: "",
|
||||
});
|
||||
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
|
||||
const validateForm = () => {
|
||||
const newErrors: Record<string, string> = {};
|
||||
|
||||
if (!userData.email) {
|
||||
newErrors.email = "Email is required";
|
||||
} else if (!/\S+@\S+\.\S+/.test(userData.email)) {
|
||||
newErrors.email = "Email is invalid";
|
||||
}
|
||||
|
||||
if (!userData.password) {
|
||||
newErrors.password = "Password is required";
|
||||
} else if (userData.password.length < 6) {
|
||||
newErrors.password = "Password must be at least 6 characters";
|
||||
}
|
||||
|
||||
if (!userData.firstName) {
|
||||
newErrors.firstName = "First name is required";
|
||||
}
|
||||
|
||||
if (!userData.lastName) {
|
||||
newErrors.lastName = "Last name is required";
|
||||
}
|
||||
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (validateForm()) {
|
||||
onSave(userData);
|
||||
// Reset form
|
||||
setUserData({
|
||||
email: "",
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
role: "user",
|
||||
status: "active",
|
||||
password: "",
|
||||
});
|
||||
setErrors({});
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputChange = (field: keyof typeof userData, value: string) => {
|
||||
setUserData((prev) => ({ ...prev, [field]: value }));
|
||||
// Clear error when field is edited
|
||||
if (errors[field]) {
|
||||
setErrors((prev) => {
|
||||
const newErrors = { ...prev };
|
||||
delete newErrors[field];
|
||||
return newErrors;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={onOpenChange}>
|
||||
<SheetContent className="sm:max-w-md">
|
||||
<SheetHeader>
|
||||
<SheetTitle>Add New User</SheetTitle>
|
||||
<SheetDescription>
|
||||
Create a new user account with Supabase authentication.
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="email" className="text-right">
|
||||
Email
|
||||
</Label>
|
||||
<Input
|
||||
id="email"
|
||||
value={userData.email}
|
||||
onChange={(e) => handleInputChange("email", e.target.value)}
|
||||
className="col-span-3"
|
||||
/>
|
||||
{errors.email && (
|
||||
<p className="col-span-3 col-start-2 text-sm text-red-500">
|
||||
{errors.email}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="password" className="text-right">
|
||||
Password
|
||||
</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
value={userData.password}
|
||||
onChange={(e) => handleInputChange("password", e.target.value)}
|
||||
className="col-span-3"
|
||||
/>
|
||||
{errors.password && (
|
||||
<p className="col-span-3 col-start-2 text-sm text-red-500">
|
||||
{errors.password}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="firstName" className="text-right">
|
||||
First Name
|
||||
</Label>
|
||||
<Input
|
||||
id="firstName"
|
||||
value={userData.firstName}
|
||||
onChange={(e) => handleInputChange("firstName", e.target.value)}
|
||||
className="col-span-3"
|
||||
/>
|
||||
{errors.firstName && (
|
||||
<p className="col-span-3 col-start-2 text-sm text-red-500">
|
||||
{errors.firstName}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="lastName" className="text-right">
|
||||
Last Name
|
||||
</Label>
|
||||
<Input
|
||||
id="lastName"
|
||||
value={userData.lastName}
|
||||
onChange={(e) => handleInputChange("lastName", e.target.value)}
|
||||
className="col-span-3"
|
||||
/>
|
||||
{errors.lastName && (
|
||||
<p className="col-span-3 col-start-2 text-sm text-red-500">
|
||||
{errors.lastName}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="role" className="text-right">
|
||||
Role
|
||||
</Label>
|
||||
<Select
|
||||
value={userData.role}
|
||||
onValueChange={(value: "admin" | "staff" | "user") =>
|
||||
handleInputChange("role", value)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="col-span-3">
|
||||
<SelectValue placeholder="Select a role" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="admin">Admin</SelectItem>
|
||||
<SelectItem value="staff">Staff</SelectItem>
|
||||
<SelectItem value="user">User</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="status" className="text-right">
|
||||
Status
|
||||
</Label>
|
||||
<Select
|
||||
value={userData.status}
|
||||
onValueChange={(value: "active" | "inactive") =>
|
||||
handleInputChange("status", value)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="col-span-3">
|
||||
<SelectValue placeholder="Select a status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="active">Active</SelectItem>
|
||||
<SelectItem value="inactive">Inactive</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<SheetFooter>
|
||||
<Button type="submit" onClick={handleSubmit}>
|
||||
Add User
|
||||
</Button>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,232 @@
|
|||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { type ColumnDef } from "@tanstack/react-table";
|
||||
import { ArrowUpDown, Filter, MoreVertical } from "lucide-react";
|
||||
|
||||
import { Button } from "@/app/_components/ui/button";
|
||||
import { Checkbox } from "@/app/_components/ui/checkbox";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/app/_components/ui/dropdown-menu";
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/app/_components/ui/avatar";
|
||||
|
||||
import { User } from "@/types/user";
|
||||
|
||||
interface ColumnOptions {
|
||||
openEditSheet: (user: User) => void;
|
||||
handleRoleFilter: (role: string) => void;
|
||||
handleStatusFilter: (status: string) => void;
|
||||
}
|
||||
|
||||
export const useColumns = ({
|
||||
openEditSheet,
|
||||
handleRoleFilter,
|
||||
handleStatusFilter,
|
||||
}: ColumnOptions) => {
|
||||
// Use useMemo to prevent unnecessary re-creation of columns array
|
||||
const columns = useMemo<ColumnDef<User>[]>(
|
||||
() => [
|
||||
{
|
||||
id: "select",
|
||||
header: ({ table }) => (
|
||||
<Checkbox
|
||||
checked={
|
||||
table.getIsAllPageRowsSelected() ||
|
||||
(table.getIsSomePageRowsSelected() && "indeterminate")
|
||||
}
|
||||
onCheckedChange={(value) =>
|
||||
table.toggleAllPageRowsSelected(!!value)
|
||||
}
|
||||
aria-label="Select all"
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
aria-label="Select row"
|
||||
/>
|
||||
),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
accessorKey: "usersColoumn",
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() =>
|
||||
column.toggleSorting(column.getIsSorted() === "asc")
|
||||
}
|
||||
>
|
||||
Users
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const user = row.original;
|
||||
return (
|
||||
<div className="flex items-center space-x-4">
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src={user.avatar}
|
||||
alt={`${user.firstName} ${user.lastName}`}
|
||||
/>
|
||||
<AvatarFallback>
|
||||
{user.firstName[0]}
|
||||
{user.lastName[0]}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<div className="font-medium">
|
||||
{user.firstName} {user.lastName}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{user.email}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
filterFn: (row, id, filterValue) => {
|
||||
const searchTerm = filterValue.toLowerCase();
|
||||
const user = row.original;
|
||||
return (
|
||||
user.firstName.toLowerCase().includes(searchTerm) ||
|
||||
user.lastName.toLowerCase().includes(searchTerm) ||
|
||||
user.email.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "role",
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<span>Role</span>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="sm" className="h-8 w-8 p-0">
|
||||
<Filter className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Filter by Role</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => handleRoleFilter("all")}>
|
||||
All
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => handleRoleFilter("admin")}>
|
||||
Admin
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => handleRoleFilter("staff")}>
|
||||
Staff
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => handleRoleFilter("user")}>
|
||||
User
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "status",
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<span>Status</span>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="sm" className="h-8 w-8 p-0">
|
||||
<Filter className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Filter by Status</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => handleStatusFilter("all")}>
|
||||
All
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleStatusFilter("active")}
|
||||
>
|
||||
Active
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleStatusFilter("inactive")}
|
||||
>
|
||||
Inactive
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const status = row.getValue("status") as string;
|
||||
return (
|
||||
<div
|
||||
className={`capitalize ${status === "active" ? "text-green-600" : "text-red-600"}`}
|
||||
>
|
||||
{status}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "lastSignedIn",
|
||||
header: "Last Sign In",
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
const user = row.original;
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||
<span className="sr-only">Open menu</span>
|
||||
<MoreVertical className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
onClick={() => navigator.clipboard.writeText(user.id)}
|
||||
>
|
||||
Copy user ID
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => openEditSheet(user)}>
|
||||
Edit user
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="text-red-600">
|
||||
Delete user
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[openEditSheet, handleRoleFilter, handleStatusFilter]
|
||||
);
|
||||
|
||||
return columns;
|
||||
};
|
|
@ -1,14 +1,32 @@
|
|||
"use client";
|
||||
import { Button } from "@/app/_components/ui/button";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { z } from "zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { format } from "date-fns";
|
||||
import { CalendarIcon, X } from "lucide-react";
|
||||
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetFooter,
|
||||
SheetClose,
|
||||
} from "@/app/_components/ui/sheet";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/app/_components/ui/form";
|
||||
import { Input } from "@/app/_components/ui/input";
|
||||
import { Button } from "@/app/_components/ui/button";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
|
@ -16,94 +34,210 @@ import {
|
|||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/app/_components/ui/select";
|
||||
import { Textarea } from "@/app/_components/ui/textarea";
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
} from "@/app/_components/ui/sheet";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/app/_components/ui/avatar";
|
||||
import { Calendar } from "@/app/_components/ui/calendar";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/app/_components/ui/popover";
|
||||
import { Separator } from "@/app/_components/ui/separator";
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/app/_components/ui/tabs";
|
||||
import { User } from "./users-table";
|
||||
|
||||
import type { User } from "./users-table";
|
||||
import { useCallback } from "react";
|
||||
|
||||
const formSchema = z.object({
|
||||
firstName: z.string().min(2, {
|
||||
message: "First name must be at least 2 characters.",
|
||||
}),
|
||||
lastName: z.string().min(2, {
|
||||
message: "Last name must be at least 2 characters.",
|
||||
}),
|
||||
email: z.string().email({
|
||||
message: "Please enter a valid email address.",
|
||||
}),
|
||||
// Create a schema for form validation
|
||||
const userFormSchema = z.object({
|
||||
// User fields
|
||||
email: z.string().email({ message: "Please enter a valid email address" }),
|
||||
firstName: z.string().min(1, { message: "First name is required" }),
|
||||
lastName: z.string().min(1, { message: "Last name is required" }),
|
||||
avatar: z.string().optional(),
|
||||
role: z.enum(["admin", "staff", "user"]),
|
||||
status: z.enum(["active", "inactive"]),
|
||||
password: z
|
||||
.string()
|
||||
.min(8, { message: "Password must be at least 8 characters long" })
|
||||
.optional(),
|
||||
|
||||
// Profile fields
|
||||
bio: z.string().optional(),
|
||||
phone: z.string().optional(),
|
||||
address: z.string().optional(),
|
||||
city: z.string().optional(),
|
||||
country: z.string().optional(),
|
||||
birthDate: z.date().optional(),
|
||||
});
|
||||
|
||||
type UserFormValues = z.infer<typeof userFormSchema>;
|
||||
|
||||
// Extended user type to include profile information
|
||||
type UserWithProfile = User & {
|
||||
profile?: {
|
||||
bio?: string;
|
||||
phone?: string;
|
||||
address?: string;
|
||||
city?: string;
|
||||
country?: string;
|
||||
birthDate?: Date;
|
||||
};
|
||||
};
|
||||
|
||||
interface EditUserSheetProps {
|
||||
isOpen: boolean;
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
selectedUser: User | null;
|
||||
onUserUpdate: (updatedUser: User) => void;
|
||||
user?: UserWithProfile | null;
|
||||
onSave: (user: UserWithProfile) => void;
|
||||
isNew?: boolean;
|
||||
}
|
||||
|
||||
export function EditUserSheet({
|
||||
isOpen,
|
||||
open,
|
||||
onOpenChange,
|
||||
selectedUser,
|
||||
onUserUpdate,
|
||||
user,
|
||||
onSave,
|
||||
isNew = false,
|
||||
}: EditUserSheetProps) {
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
const form = useForm<UserFormValues>({
|
||||
resolver: zodResolver(userFormSchema),
|
||||
defaultValues: {
|
||||
firstName: selectedUser?.firstName || "",
|
||||
lastName: selectedUser?.lastName || "",
|
||||
email: selectedUser?.email || "",
|
||||
role: selectedUser?.role || "user",
|
||||
status: selectedUser?.status || "inactive",
|
||||
email: "",
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
role: "user",
|
||||
password: isNew ? "" : undefined,
|
||||
bio: "",
|
||||
phone: "",
|
||||
address: "",
|
||||
city: "",
|
||||
country: "",
|
||||
birthDate: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = useCallback(
|
||||
(values: z.infer<typeof formSchema>) => {
|
||||
if (!selectedUser) return;
|
||||
// Update form when user changes
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
const birthDate = user.profile?.birthDate
|
||||
? new Date(user.profile.birthDate)
|
||||
: undefined;
|
||||
|
||||
const updatedUser: User = {
|
||||
...selectedUser,
|
||||
firstName: values.firstName,
|
||||
lastName: values.lastName,
|
||||
email: values.email,
|
||||
role: values.role,
|
||||
status: values.status,
|
||||
form.reset({
|
||||
email: user.email,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
avatar: user.avatar,
|
||||
role: user.role,
|
||||
// Don't populate password for existing users
|
||||
bio: user.profile?.bio || "",
|
||||
phone: user.profile?.phone || "",
|
||||
address: user.profile?.address || "",
|
||||
city: user.profile?.city || "",
|
||||
country: user.profile?.country || "",
|
||||
birthDate: birthDate,
|
||||
});
|
||||
} else if (isNew) {
|
||||
form.reset({
|
||||
email: "",
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
role: "user",
|
||||
password: "",
|
||||
bio: "",
|
||||
phone: "",
|
||||
address: "",
|
||||
city: "",
|
||||
country: "",
|
||||
birthDate: undefined,
|
||||
});
|
||||
}
|
||||
}, [user, isNew, form]);
|
||||
|
||||
const onSubmit = (data: UserFormValues) => {
|
||||
// Prepare the user object with profile
|
||||
const updatedUser: UserWithProfile = {
|
||||
id: user?.id || "new-id", // In a real app, this would be handled by the backend
|
||||
email: data.email,
|
||||
firstName: data.firstName || "",
|
||||
lastName: data.lastName || "",
|
||||
avatar: data.avatar || "/placeholder.svg?height=40&width=40",
|
||||
role: data.role,
|
||||
status: user?.status || "active",
|
||||
lastSignedIn:
|
||||
user?.lastSignedIn || new Date().toISOString().split("T")[0],
|
||||
profile: {
|
||||
bio: data.bio,
|
||||
phone: data.phone,
|
||||
address: data.address,
|
||||
city: data.city,
|
||||
country: data.country,
|
||||
birthDate: data.birthDate,
|
||||
},
|
||||
};
|
||||
|
||||
onUserUpdate(updatedUser);
|
||||
// Save the user
|
||||
onSave(updatedUser);
|
||||
|
||||
// Close the sheet
|
||||
onOpenChange(false);
|
||||
},
|
||||
[selectedUser, onUserUpdate, onOpenChange]
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Sheet open={isOpen} onOpenChange={onOpenChange}>
|
||||
<SheetContent side="right">
|
||||
<Sheet open={open} onOpenChange={onOpenChange}>
|
||||
<SheetContent className="sm:max-w-xl overflow-y-auto">
|
||||
<SheetHeader>
|
||||
<SheetTitle>Edit User</SheetTitle>
|
||||
<SheetTitle>{isNew ? "Add New User" : "Edit User"}</SheetTitle>
|
||||
<SheetDescription>
|
||||
Make changes to the user profile here. Click save when you're done.
|
||||
{isNew
|
||||
? "Fill in the details to create a new user account."
|
||||
: "Make changes to the user profile here."}
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
<div className="py-4">
|
||||
<Tabs defaultValue="user" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="user">User Details</TabsTrigger>
|
||||
<TabsTrigger value="profile">Profile Information</TabsTrigger>
|
||||
</TabsList>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6 pt-6"
|
||||
>
|
||||
<TabsContent value="user" className="space-y-4">
|
||||
<div className="flex justify-center mb-6">
|
||||
<Avatar className="h-24 w-24">
|
||||
<AvatarImage
|
||||
src={
|
||||
form.watch("avatar") ||
|
||||
"/placeholder.svg?height=96&width=96"
|
||||
}
|
||||
alt="User avatar"
|
||||
/>
|
||||
<AvatarFallback>
|
||||
{form.watch("firstName")?.charAt(0) || ""}
|
||||
{form.watch("lastName")?.charAt(0) || ""}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="firstName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>First name</FormLabel>
|
||||
<FormLabel>First Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="First name" {...field} />
|
||||
</FormControl>
|
||||
|
@ -116,7 +250,7 @@ export function EditUserSheet({
|
|||
name="lastName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Last name</FormLabel>
|
||||
<FormLabel>Last Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Last name" {...field} />
|
||||
</FormControl>
|
||||
|
@ -124,6 +258,8 @@ export function EditUserSheet({
|
|||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
|
@ -131,12 +267,40 @@ export function EditUserSheet({
|
|||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Email" {...field} />
|
||||
<Input
|
||||
placeholder="Email address"
|
||||
type="email"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{isNew && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Set password"
|
||||
type="password"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Minimum 8 characters
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="role"
|
||||
|
@ -146,10 +310,13 @@ export function EditUserSheet({
|
|||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
value={field.value}
|
||||
>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a role" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="admin">Admin</SelectItem>
|
||||
<SelectItem value="staff">Staff</SelectItem>
|
||||
|
@ -160,31 +327,155 @@ export function EditUserSheet({
|
|||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="status"
|
||||
name="avatar"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Status</FormLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Select a status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="active">Active</SelectItem>
|
||||
<SelectItem value="inactive">Inactive</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormLabel>Avatar URL</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Avatar URL" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit">Save changes</Button>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="profile" className="space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="bio"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Bio</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder="Tell us a little about this user"
|
||||
className="resize-none"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="phone"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Phone</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Phone number" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="address"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Address</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Street address" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="city"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>City</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="City" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="country"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Country</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Country" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="birthDate"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormLabel>Date of Birth</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className={`w-full pl-3 text-left font-normal ${
|
||||
!field.value && "text-muted-foreground"
|
||||
}`}
|
||||
>
|
||||
{field.value ? (
|
||||
format(field.value, "PPP")
|
||||
) : (
|
||||
<span>Pick a date</span>
|
||||
)}
|
||||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={field.value}
|
||||
onSelect={field.onChange}
|
||||
disabled={(date: Date) =>
|
||||
date > new Date() ||
|
||||
date < new Date("1900-01-01")
|
||||
}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<SheetFooter className="pt-4">
|
||||
<SheetClose asChild>
|
||||
<Button variant="outline" type="button">
|
||||
Cancel
|
||||
</Button>
|
||||
</SheetClose>
|
||||
<Button type="submit">
|
||||
{isNew ? "Create User" : "Save Changes"}
|
||||
</Button>
|
||||
</SheetFooter>
|
||||
</form>
|
||||
</Form>
|
||||
</Tabs>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
|
|
|
@ -183,15 +183,15 @@ export function UsersTable() {
|
|||
useEffect(() => {
|
||||
if (searchValue) {
|
||||
setColumnFilters((prev) => {
|
||||
// Remove existing kolomusers filter if it exists
|
||||
const filtered = prev.filter((filter) => filter.id !== "kolomusers");
|
||||
// Remove existing usersColoumn filter if it exists
|
||||
const filtered = prev.filter((filter) => filter.id !== "usersColoumn");
|
||||
// Add the new search filter
|
||||
return [...filtered, { id: "kolomusers", value: searchValue }];
|
||||
return [...filtered, { id: "usersColoumn", value: searchValue }];
|
||||
});
|
||||
} else {
|
||||
// Remove the filter if search is empty
|
||||
setColumnFilters((prev) =>
|
||||
prev.filter((filter) => filter.id !== "kolomusers")
|
||||
prev.filter((filter) => filter.id !== "usersColoumn")
|
||||
);
|
||||
}
|
||||
}, [searchValue]);
|
||||
|
@ -256,7 +256,7 @@ export function UsersTable() {
|
|||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
accessorKey: "kolomusers",
|
||||
accessorKey: "usersColoumn",
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
|
@ -575,10 +575,10 @@ export function UsersTable() {
|
|||
</div>
|
||||
</div>
|
||||
<EditUserSheet
|
||||
isOpen={isSheetOpen}
|
||||
onOpenChange={setIsSheetOpen}
|
||||
selectedUser={selectedUser}
|
||||
onUserUpdate={handleUserUpdate}
|
||||
open={isSheetOpen}
|
||||
onOpenChange={() => setIsSheetOpen(false)}
|
||||
user={selectedUser}
|
||||
onSave={handleUserUpdate}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"@radix-ui/react-separator": "^1.1.2",
|
||||
"@radix-ui/react-slot": "^1.1.2",
|
||||
"@radix-ui/react-switch": "^1.1.3",
|
||||
"@radix-ui/react-tabs": "^1.1.3",
|
||||
"@radix-ui/react-toast": "^1.2.6",
|
||||
"@radix-ui/react-tooltip": "^1.1.8",
|
||||
"@react-email/components": "0.0.33",
|
||||
|
@ -33,7 +34,7 @@
|
|||
"autoprefixer": "10.4.20",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"framer-motion": "^12.4.7",
|
||||
"input-otp": "^1.4.2",
|
||||
"lucide-react": "^0.468.0",
|
||||
|
@ -43,6 +44,7 @@
|
|||
"next-themes": "^0.4.3",
|
||||
"prettier": "^3.3.3",
|
||||
"react": "18.3.1",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"resend": "^4.1.2",
|
||||
|
@ -2397,6 +2399,36 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tabs": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.3.tgz",
|
||||
"integrity": "sha512-9mFyI30cuRDImbmFF6O2KUJdgEOsGh9Vmx9x/Dh9tOhL7BngmQPQfwW4aejKm5OHpfWIdmeV6ySyuxoOGjtNng==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.1",
|
||||
"@radix-ui/react-context": "1.1.1",
|
||||
"@radix-ui/react-direction": "1.1.0",
|
||||
"@radix-ui/react-id": "1.1.0",
|
||||
"@radix-ui/react-presence": "1.1.2",
|
||||
"@radix-ui/react-primitive": "2.0.2",
|
||||
"@radix-ui/react-roving-focus": "1.1.2",
|
||||
"@radix-ui/react-use-controllable-state": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-toast": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.6.tgz",
|
||||
|
@ -3910,9 +3942,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
|
||||
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
|
@ -5863,6 +5895,20 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-day-picker": {
|
||||
"version": "8.10.1",
|
||||
"resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz",
|
||||
"integrity": "sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/gpbl"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"date-fns": "^2.28.0 || ^3.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
|
@ -6906,9 +6952,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/supabase": {
|
||||
"version": "2.12.1",
|
||||
"resolved": "https://registry.npmjs.org/supabase/-/supabase-2.12.1.tgz",
|
||||
"integrity": "sha512-vB6LX1KGrqku8AFlp2vJw49IUB9g6Rz2b84qpcWSZ3mMDFumA6hDSbXbFJUnr3hcvyPzoOsQlhMTZN7a6o3hfA==",
|
||||
"version": "2.15.8",
|
||||
"resolved": "https://registry.npmjs.org/supabase/-/supabase-2.15.8.tgz",
|
||||
"integrity": "sha512-yY4kVpdd7x9u5QqTW/8zUXIrMgdkBDGqQwkDugBLe8uoFdH9tVZKt0L5RmuM21RJ0MEQkby2sQrTfiXvgGyx9w==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
"@radix-ui/react-separator": "^1.1.2",
|
||||
"@radix-ui/react-slot": "^1.1.2",
|
||||
"@radix-ui/react-switch": "^1.1.3",
|
||||
"@radix-ui/react-tabs": "^1.1.3",
|
||||
"@radix-ui/react-toast": "^1.2.6",
|
||||
"@radix-ui/react-tooltip": "^1.1.8",
|
||||
"@react-email/components": "0.0.33",
|
||||
|
@ -34,7 +35,7 @@
|
|||
"autoprefixer": "10.4.20",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"framer-motion": "^12.4.7",
|
||||
"input-otp": "^1.4.2",
|
||||
"lucide-react": "^0.468.0",
|
||||
|
@ -44,6 +45,7 @@
|
|||
"next-themes": "^0.4.3",
|
||||
"prettier": "^3.3.3",
|
||||
"react": "18.3.1",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"resend": "^4.1.2",
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
-- CreateEnum
|
||||
CREATE TYPE "roles" AS ENUM ('admin', 'staff', 'user');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "users" (
|
||||
"id" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"emailVerified" BOOLEAN NOT NULL DEFAULT false,
|
||||
"password" TEXT,
|
||||
"firstName" TEXT,
|
||||
"lastName" TEXT,
|
||||
"avatar" TEXT,
|
||||
"role" "roles" NOT NULL DEFAULT 'user',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"lastSignedIn" TIMESTAMP(3),
|
||||
"metadata" JSONB,
|
||||
|
||||
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "profiles" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"bio" TEXT,
|
||||
"phone" TEXT,
|
||||
"address" TEXT,
|
||||
"city" TEXT,
|
||||
"country" TEXT,
|
||||
"birthDate" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "profiles_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "profiles_userId_key" ON "profiles"("userId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "profiles" ADD CONSTRAINT "profiles_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@ -1,18 +0,0 @@
|
|||
-- CreateEnum
|
||||
CREATE TYPE "status_contact_messages" AS ENUM ('new', 'read', 'replied', 'resolved');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "contact_messages" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT,
|
||||
"email" TEXT,
|
||||
"phone" TEXT,
|
||||
"message_type" TEXT,
|
||||
"message_type_label" TEXT,
|
||||
"message" TEXT,
|
||||
"status" "status_contact_messages" NOT NULL DEFAULT 'new',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "contact_messages_pkey" PRIMARY KEY ("id")
|
||||
);
|
|
@ -1,106 +0,0 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `createdAt` on the `contact_messages` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `updatedAt` on the `contact_messages` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `birthDate` on the `profiles` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `userId` on the `profiles` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `createdAt` on the `users` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `emailVerified` on the `users` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `firstName` on the `users` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `lastName` on the `users` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `lastSignedIn` on the `users` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `updatedAt` on the `users` table. All the data in the column will be lost.
|
||||
- A unique constraint covering the columns `[user_id]` on the table `profiles` will be added. If there are existing duplicate values, this will fail.
|
||||
- Added the required column `user_id` to the `profiles` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `updated_at` to the `users` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- CreateExtension
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "profiles" DROP CONSTRAINT "profiles_userId_fkey";
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "profiles_userId_key";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "contact_messages" DROP COLUMN "createdAt",
|
||||
DROP COLUMN "updatedAt",
|
||||
ADD COLUMN "created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
|
||||
ADD COLUMN "updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
|
||||
ALTER COLUMN "id" SET DEFAULT gen_random_uuid();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "profiles" DROP COLUMN "birthDate",
|
||||
DROP COLUMN "userId",
|
||||
ADD COLUMN "birth_date" TIMESTAMP(3),
|
||||
ADD COLUMN "user_id" TEXT NOT NULL,
|
||||
ALTER COLUMN "id" SET DEFAULT gen_random_uuid();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" DROP COLUMN "createdAt",
|
||||
DROP COLUMN "emailVerified",
|
||||
DROP COLUMN "firstName",
|
||||
DROP COLUMN "lastName",
|
||||
DROP COLUMN "lastSignedIn",
|
||||
DROP COLUMN "updatedAt",
|
||||
ADD COLUMN "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
ADD COLUMN "email_verified" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "first_name" TEXT,
|
||||
ADD COLUMN "last_name" TEXT,
|
||||
ADD COLUMN "last_signed_in" TIMESTAMP(3),
|
||||
ADD COLUMN "updated_at" TIMESTAMP(3) NOT NULL;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "nav_items" (
|
||||
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"title" VARCHAR(255) NOT NULL,
|
||||
"url" VARCHAR(255) NOT NULL,
|
||||
"icon" VARCHAR(100) NOT NULL,
|
||||
"is_active" BOOLEAN NOT NULL DEFAULT false,
|
||||
"order_seq" INTEGER NOT NULL,
|
||||
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
|
||||
"updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
|
||||
"created_by" UUID,
|
||||
"updated_by" UUID,
|
||||
|
||||
CONSTRAINT "nav_items_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "sub_items" (
|
||||
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"title" VARCHAR(255) NOT NULL,
|
||||
"url" VARCHAR(255) NOT NULL,
|
||||
"order_seq" INTEGER NOT NULL,
|
||||
"nav_item_id" UUID NOT NULL,
|
||||
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
|
||||
"updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
|
||||
"created_by" UUID,
|
||||
"updated_by" UUID,
|
||||
|
||||
CONSTRAINT "sub_items_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "nav_items_title_idx" ON "nav_items"("title");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "nav_items_is_active_idx" ON "nav_items"("is_active");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "sub_items_nav_item_id_idx" ON "sub_items"("nav_item_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "sub_items_title_idx" ON "sub_items"("title");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "profiles_user_id_key" ON "profiles"("user_id");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "profiles" ADD CONSTRAINT "profiles_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "sub_items" ADD CONSTRAINT "sub_items_nav_item_id_fkey" FOREIGN KEY ("nav_item_id") REFERENCES "nav_items"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@ -1,108 +0,0 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- The primary key for the `contact_messages` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||
- The `id` column on the `contact_messages` table would be dropped and recreated. This will lead to data loss if there is data in the column.
|
||||
- You are about to alter the column `name` on the `contact_messages` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(255)`.
|
||||
- You are about to alter the column `email` on the `contact_messages` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(255)`.
|
||||
- You are about to alter the column `phone` on the `contact_messages` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(20)`.
|
||||
- You are about to alter the column `message_type` on the `contact_messages` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(50)`.
|
||||
- You are about to alter the column `message_type_label` on the `contact_messages` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(50)`.
|
||||
- The primary key for the `profiles` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||
- The `id` column on the `profiles` table would be dropped and recreated. This will lead to data loss if there is data in the column.
|
||||
- You are about to alter the column `phone` on the `profiles` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(20)`.
|
||||
- You are about to alter the column `address` on the `profiles` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(255)`.
|
||||
- You are about to alter the column `city` on the `profiles` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(100)`.
|
||||
- You are about to alter the column `country` on the `profiles` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(100)`.
|
||||
- The primary key for the `users` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||
- You are about to alter the column `email` on the `users` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(255)`.
|
||||
- You are about to alter the column `password` on the `users` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(255)`.
|
||||
- You are about to alter the column `avatar` on the `users` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(255)`.
|
||||
- You are about to alter the column `first_name` on the `users` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(255)`.
|
||||
- You are about to alter the column `last_name` on the `users` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(255)`.
|
||||
- You are about to drop the `sub_items` table. If the table is not empty, all the data it contains will be lost.
|
||||
- Changed the type of `user_id` on the `profiles` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required.
|
||||
- Changed the type of `id` on the `users` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "profiles" DROP CONSTRAINT "profiles_user_id_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "sub_items" DROP CONSTRAINT "sub_items_nav_item_id_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "contact_messages" DROP CONSTRAINT "contact_messages_pkey",
|
||||
DROP COLUMN "id",
|
||||
ADD COLUMN "id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
ALTER COLUMN "name" SET DATA TYPE VARCHAR(255),
|
||||
ALTER COLUMN "email" SET DATA TYPE VARCHAR(255),
|
||||
ALTER COLUMN "phone" SET DATA TYPE VARCHAR(20),
|
||||
ALTER COLUMN "message_type" SET DATA TYPE VARCHAR(50),
|
||||
ALTER COLUMN "message_type_label" SET DATA TYPE VARCHAR(50),
|
||||
ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now(),
|
||||
ADD CONSTRAINT "contact_messages_pkey" PRIMARY KEY ("id");
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "nav_items" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "profiles" DROP CONSTRAINT "profiles_pkey",
|
||||
DROP COLUMN "id",
|
||||
ADD COLUMN "id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
ALTER COLUMN "phone" SET DATA TYPE VARCHAR(20),
|
||||
ALTER COLUMN "address" SET DATA TYPE VARCHAR(255),
|
||||
ALTER COLUMN "city" SET DATA TYPE VARCHAR(100),
|
||||
ALTER COLUMN "country" SET DATA TYPE VARCHAR(100),
|
||||
DROP COLUMN "user_id",
|
||||
ADD COLUMN "user_id" UUID NOT NULL,
|
||||
ADD CONSTRAINT "profiles_pkey" PRIMARY KEY ("id");
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" DROP CONSTRAINT "users_pkey",
|
||||
DROP COLUMN "id",
|
||||
ADD COLUMN "id" UUID NOT NULL,
|
||||
ALTER COLUMN "email" SET DATA TYPE VARCHAR(255),
|
||||
ALTER COLUMN "password" SET DATA TYPE VARCHAR(255),
|
||||
ALTER COLUMN "avatar" SET DATA TYPE VARCHAR(255),
|
||||
ALTER COLUMN "first_name" SET DATA TYPE VARCHAR(255),
|
||||
ALTER COLUMN "last_name" SET DATA TYPE VARCHAR(255),
|
||||
ADD CONSTRAINT "users_pkey" PRIMARY KEY ("id");
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "sub_items";
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "nav_sub_items" (
|
||||
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"title" VARCHAR(255) NOT NULL,
|
||||
"url" VARCHAR(255) NOT NULL,
|
||||
"order_seq" INTEGER NOT NULL,
|
||||
"nav_item_id" UUID NOT NULL,
|
||||
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
|
||||
"updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
|
||||
"created_by" UUID,
|
||||
"updated_by" UUID,
|
||||
|
||||
CONSTRAINT "nav_sub_items_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "nav_sub_items_nav_item_id_idx" ON "nav_sub_items"("nav_item_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "nav_sub_items_title_idx" ON "nav_sub_items"("title");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "profiles_user_id_key" ON "profiles"("user_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "users_role_idx" ON "users"("role");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "profiles" ADD CONSTRAINT "profiles_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "nav_sub_items" ADD CONSTRAINT "nav_sub_items_nav_item_id_fkey" FOREIGN KEY ("nav_item_id") REFERENCES "nav_items"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@ -1,63 +0,0 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "cities" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "contact_messages" ALTER COLUMN "created_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "crime_cases" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "crime_categories" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "crimes" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "demographics" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "districts" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "geographics" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "nav_items" ALTER COLUMN "created_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "nav_sub_items" ALTER COLUMN "created_at" SET DEFAULT now();
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "nav_sub_sub_items" (
|
||||
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"title" VARCHAR(255) NOT NULL,
|
||||
"url" VARCHAR(255) NOT NULL,
|
||||
"slug" VARCHAR(255) NOT NULL,
|
||||
"icon" VARCHAR(100) NOT NULL,
|
||||
"is_active" BOOLEAN NOT NULL DEFAULT false,
|
||||
"order_seq" INTEGER NOT NULL,
|
||||
"nav_sub_item_id" UUID NOT NULL,
|
||||
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
|
||||
"updated_at" TIMESTAMPTZ(6) NOT NULL,
|
||||
"created_by" UUID,
|
||||
"updated_by" UUID,
|
||||
|
||||
CONSTRAINT "nav_sub_sub_items_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "nav_sub_sub_items_nav_sub_item_id_idx" ON "nav_sub_sub_items"("nav_sub_item_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "nav_sub_sub_items_title_idx" ON "nav_sub_sub_items"("title");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "nav_sub_sub_items" ADD CONSTRAINT "nav_sub_sub_items_nav_sub_item_id_fkey" FOREIGN KEY ("nav_sub_item_id") REFERENCES "nav_sub_items"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@ -1,32 +1,65 @@
|
|||
/*
|
||||
Warnings:
|
||||
-- CreateExtension
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
||||
|
||||
- Added the required column `slug` to the `nav_items` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `icon` to the `nav_sub_items` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `slug` to the `nav_sub_items` table without a default value. This is not possible if the table is not empty.
|
||||
-- CreateEnum
|
||||
CREATE TYPE "roles" AS ENUM ('admin', 'staff', 'user');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "status_contact_messages" AS ENUM ('new', 'read', 'replied', 'resolved');
|
||||
|
||||
*/
|
||||
-- CreateEnum
|
||||
CREATE TYPE "crime_rates" AS ENUM ('low', 'medium', 'high');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "crime_status" AS ENUM ('new', 'in_progress', 'resolved');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "contact_messages" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" DROP DEFAULT;
|
||||
-- CreateTable
|
||||
CREATE TABLE "users" (
|
||||
"id" UUID NOT NULL,
|
||||
"email" VARCHAR(255) NOT NULL,
|
||||
"email_verified" BOOLEAN NOT NULL DEFAULT false,
|
||||
"password" VARCHAR(255),
|
||||
"first_name" VARCHAR(255),
|
||||
"last_name" VARCHAR(255),
|
||||
"avatar" VARCHAR(255),
|
||||
"role" "roles" NOT NULL DEFAULT 'user',
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
"last_signed_in" TIMESTAMP(3),
|
||||
"metadata" JSONB,
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "nav_items" ADD COLUMN "slug" VARCHAR(255) NOT NULL,
|
||||
ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" DROP DEFAULT;
|
||||
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "nav_sub_items" ADD COLUMN "icon" VARCHAR(100) NOT NULL,
|
||||
ADD COLUMN "is_active" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "slug" VARCHAR(255) NOT NULL,
|
||||
ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" DROP DEFAULT;
|
||||
-- CreateTable
|
||||
CREATE TABLE "profiles" (
|
||||
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"user_id" UUID NOT NULL,
|
||||
"bio" TEXT,
|
||||
"phone" VARCHAR(20),
|
||||
"address" VARCHAR(255),
|
||||
"city" VARCHAR(100),
|
||||
"country" VARCHAR(100),
|
||||
"birth_date" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "profiles_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "contact_messages" (
|
||||
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"name" VARCHAR(255),
|
||||
"email" VARCHAR(255),
|
||||
"phone" VARCHAR(20),
|
||||
"message_type" VARCHAR(50),
|
||||
"message_type_label" VARCHAR(50),
|
||||
"message" TEXT,
|
||||
"status" "status_contact_messages" NOT NULL DEFAULT 'new',
|
||||
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
|
||||
"updated_at" TIMESTAMPTZ(6) NOT NULL,
|
||||
|
||||
CONSTRAINT "contact_messages_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "cities" (
|
||||
|
@ -127,6 +160,18 @@ CREATE TABLE "crime_categories" (
|
|||
CONSTRAINT "crime_categories_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "users_role_idx" ON "users"("role");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "profiles_user_id_key" ON "profiles"("user_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "profiles_user_id_idx" ON "profiles"("user_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "cities_name_idx" ON "cities"("name");
|
||||
|
||||
|
@ -148,8 +193,8 @@ CREATE UNIQUE INDEX "crimes_district_id_year_key" ON "crimes"("district_id", "ye
|
|||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "crimes_city_id_year_key" ON "crimes"("city_id", "year");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "profiles_user_id_idx" ON "profiles"("user_id");
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "profiles" ADD CONSTRAINT "profiles_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "cities" ADD CONSTRAINT "cities_geographic_id_fkey" FOREIGN KEY ("geographic_id") REFERENCES "geographics"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `last_signed_in` on the `users` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `metadata` on the `users` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `password` on the `users` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "cities" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "contact_messages" ALTER COLUMN "created_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "crime_cases" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "crime_categories" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "crimes" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "demographics" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "districts" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "geographics" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" DROP COLUMN "last_signed_in",
|
||||
DROP COLUMN "metadata",
|
||||
DROP COLUMN "password",
|
||||
ADD COLUMN "banned_until" TIMESTAMP(3),
|
||||
ADD COLUMN "confirmation_sent_at" TIMESTAMP(3),
|
||||
ADD COLUMN "confirmation_token" VARCHAR(255),
|
||||
ADD COLUMN "deleted_at" TIMESTAMP(3),
|
||||
ADD COLUMN "email_change" VARCHAR(255),
|
||||
ADD COLUMN "email_change_sent_at" TIMESTAMP(3),
|
||||
ADD COLUMN "email_change_token" VARCHAR(255),
|
||||
ADD COLUMN "email_confirmed_at" TIMESTAMP(3),
|
||||
ADD COLUMN "encrypted_password" VARCHAR(255),
|
||||
ADD COLUMN "is_anonymous" BOOLEAN DEFAULT false,
|
||||
ADD COLUMN "is_sso_user" BOOLEAN DEFAULT false,
|
||||
ADD COLUMN "last_sign_in_at" TIMESTAMP(3),
|
||||
ADD COLUMN "phone" VARCHAR(20),
|
||||
ADD COLUMN "phone_confirmed_at" TIMESTAMP(3),
|
||||
ADD COLUMN "providers" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
ADD COLUMN "raw_app_meta_data" JSONB,
|
||||
ADD COLUMN "raw_user_meta_data" JSONB,
|
||||
ADD COLUMN "reauthentication_sent_at" TIMESTAMP(3),
|
||||
ADD COLUMN "reauthentication_token" VARCHAR(255),
|
||||
ADD COLUMN "recovery_sent_at" TIMESTAMP(3),
|
||||
ADD COLUMN "recovery_token" VARCHAR(255);
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- The `providers` column on the `users` table would be dropped and recreated. This will lead to data loss if there is data in the column.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "cities" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "contact_messages" ALTER COLUMN "created_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "crime_cases" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "crime_categories" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "crimes" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "demographics" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "districts" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "geographics" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" DROP COLUMN "providers",
|
||||
ADD COLUMN "providers" JSONB DEFAULT '[]';
|
|
@ -20,15 +20,33 @@ model User {
|
|||
id String @id @db.Uuid
|
||||
email String @unique @db.VarChar(255)
|
||||
emailVerified Boolean @default(false) @map("email_verified")
|
||||
password String? @db.VarChar(255)
|
||||
encryptedPassword String? @map("encrypted_password") @db.VarChar(255)
|
||||
firstName String? @map("first_name") @db.VarChar(255)
|
||||
lastName String? @map("last_name") @db.VarChar(255)
|
||||
avatar String? @db.VarChar(255)
|
||||
role Role @default(user)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
lastSignedIn DateTime? @map("last_signed_in")
|
||||
metadata Json?
|
||||
lastSignedIn DateTime? @map("last_sign_in_at")
|
||||
phone String? @db.VarChar(20)
|
||||
phoneConfirmedAt DateTime? @map("phone_confirmed_at")
|
||||
emailConfirmedAt DateTime? @map("email_confirmed_at")
|
||||
confirmationToken String? @map("confirmation_token") @db.VarChar(255)
|
||||
confirmationSentAt DateTime? @map("confirmation_sent_at")
|
||||
recoveryToken String? @map("recovery_token") @db.VarChar(255)
|
||||
recoverySentAt DateTime? @map("recovery_sent_at")
|
||||
emailChangeToken String? @map("email_change_token") @db.VarChar(255)
|
||||
emailChange String? @map("email_change") @db.VarChar(255)
|
||||
emailChangeSentAt DateTime? @map("email_change_sent_at")
|
||||
reauthenticationToken String? @map("reauthentication_token") @db.VarChar(255)
|
||||
reauthenticationSentAt DateTime? @map("reauthentication_sent_at")
|
||||
isAnonymous Boolean? @default(false) @map("is_anonymous")
|
||||
providers Json? @default("[]")
|
||||
rawAppMetadata Json? @map("raw_app_meta_data")
|
||||
rawUserMetadata Json? @map("raw_user_meta_data")
|
||||
bannedUntil DateTime? @map("banned_until")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
isSsoUser Boolean? @default(false) @map("is_sso_user")
|
||||
|
||||
profile Profile?
|
||||
|
||||
|
|
|
@ -37,6 +37,14 @@ export const userSchema = z.object({
|
|||
|
||||
export type User = z.infer<typeof userSchema>;
|
||||
|
||||
export const userAuthSchema = userSchema.pick({
|
||||
id: true,
|
||||
email: true,
|
||||
emailVerified: true,
|
||||
role: true,
|
||||
profile: true,
|
||||
})
|
||||
|
||||
export const createUserSchema = userSchema
|
||||
.pick({
|
||||
id: true,
|
||||
|
|
|
@ -45,6 +45,11 @@ max_client_conn = 100
|
|||
# [db.vault]
|
||||
# secret_key = "env(SECRET_VALUE)"
|
||||
|
||||
[db.migrations]
|
||||
# Specifies an ordered list of schema files that describe your database.
|
||||
# Supports glob patterns relative to supabase directory: "./schemas/*.sql"
|
||||
schema_paths = []
|
||||
|
||||
[db.seed]
|
||||
# If enabled, seeds the database after migrations during a db reset.
|
||||
enabled = true
|
||||
|
@ -271,7 +276,7 @@ inspector_port = 8083
|
|||
# static_files = [ "./functions/MY_FUNCTION_NAME/*.html" ]
|
||||
|
||||
[analytics]
|
||||
enabled = true
|
||||
enabled = false
|
||||
port = 54327
|
||||
# Configure one of the supported backends: `postgres`, `bigquery`.
|
||||
backend = "postgres"
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
# Configuration for private npm package dependencies
|
||||
# For more information on using private registries with Edge Functions, see:
|
||||
# https://supabase.com/docs/guides/functions/import-maps#importing-from-private-registries
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"imports": {}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
import { Webhook } from "https://esm.sh/standardwebhooks@1.0.0";
|
||||
import { Resend } from "npm:resend";
|
||||
|
||||
const resend = new Resend("re_4EyTgztQ_3istGdHQFeSTQsLBtH6oAdub" as string);
|
||||
const hookSecret =
|
||||
"jeroAB/CXdS721OiHV0Ac0yRcxO7eNihgjblH62xMhLBNc6OwK3DQnkbrHjTlSw5anml2onNTolG3SzZ" as string;
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
if (req.method !== "POST") {
|
||||
return new Response("not allowed", { status: 400 });
|
||||
}
|
||||
|
||||
const payload = await req.text();
|
||||
const headers = Object.fromEntries(req.headers);
|
||||
const wh = new Webhook(hookSecret);
|
||||
try {
|
||||
const { user, email_data } = wh.verify(payload, headers) as {
|
||||
user: {
|
||||
email: string;
|
||||
};
|
||||
email_data: {
|
||||
token: string;
|
||||
token_hash: string;
|
||||
redirect_to: string;
|
||||
email_action_type: string;
|
||||
site_url: string;
|
||||
token_new: string;
|
||||
token_hash_new: string;
|
||||
};
|
||||
};
|
||||
|
||||
const { error } = await resend.emails.send({
|
||||
from: "welcome <onboarding@example.com>",
|
||||
to: [user.email],
|
||||
subject: "Welcome to my site!",
|
||||
text: `Confirm you signup with this code: ${email_data.token}`,
|
||||
});
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: {
|
||||
http_code: error.code,
|
||||
message: error.message,
|
||||
},
|
||||
}),
|
||||
{
|
||||
status: 401,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const responseHeaders = new Headers();
|
||||
responseHeaders.set("Content-Type", "application/json");
|
||||
return new Response(JSON.stringify({}), {
|
||||
status: 200,
|
||||
headers: responseHeaders,
|
||||
});
|
||||
});
|
|
@ -0,0 +1,212 @@
|
|||
SET statement_timeout = 0;
|
||||
SET lock_timeout = 0;
|
||||
SET idle_in_transaction_session_timeout = 0;
|
||||
SET client_encoding = 'UTF8';
|
||||
SET standard_conforming_strings = on;
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
SET check_function_bodies = false;
|
||||
SET xmloption = content;
|
||||
SET client_min_messages = warning;
|
||||
SET row_security = off;
|
||||
CREATE EXTENSION IF NOT EXISTS "pgsodium";
|
||||
COMMENT ON SCHEMA "public" IS 'standard public schema';
|
||||
CREATE EXTENSION IF NOT EXISTS "pg_graphql" WITH SCHEMA "graphql";
|
||||
CREATE EXTENSION IF NOT EXISTS "pg_stat_statements" WITH SCHEMA "extensions";
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto" WITH SCHEMA "extensions";
|
||||
CREATE EXTENSION IF NOT EXISTS "pgjwt" WITH SCHEMA "extensions";
|
||||
CREATE EXTENSION IF NOT EXISTS "supabase_vault" WITH SCHEMA "vault";
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA "extensions";
|
||||
CREATE TYPE "public"."crime_rates" AS ENUM (
|
||||
'low',
|
||||
'medium',
|
||||
'high'
|
||||
);
|
||||
ALTER TYPE "public"."crime_rates" OWNER TO "prisma";
|
||||
CREATE TYPE "public"."crime_status" AS ENUM (
|
||||
'new',
|
||||
'in_progress',
|
||||
'resolved'
|
||||
);
|
||||
ALTER TYPE "public"."crime_status" OWNER TO "prisma";
|
||||
CREATE TYPE "public"."roles" AS ENUM (
|
||||
'admin',
|
||||
'staff',
|
||||
'user'
|
||||
);
|
||||
ALTER TYPE "public"."roles" OWNER TO "prisma";
|
||||
CREATE TYPE "public"."status_contact_messages" AS ENUM (
|
||||
'new',
|
||||
'read',
|
||||
'replied',
|
||||
'resolved'
|
||||
);
|
||||
ALTER TYPE "public"."status_contact_messages" OWNER TO "prisma";
|
||||
SET default_tablespace = '';
|
||||
SET default_table_access_method = "heap";
|
||||
CREATE TABLE IF NOT EXISTS "public"."_prisma_migrations" (
|
||||
"id" character varying(36) NOT NULL,
|
||||
"checksum" character varying(64) NOT NULL,
|
||||
"finished_at" timestamp with time zone,
|
||||
"migration_name" character varying(255) NOT NULL,
|
||||
"logs" "text",
|
||||
"rolled_back_at" timestamp with time zone,
|
||||
"started_at" timestamp with time zone DEFAULT "now"() NOT NULL,
|
||||
"applied_steps_count" integer DEFAULT 0 NOT NULL
|
||||
);
|
||||
ALTER TABLE "public"."_prisma_migrations" OWNER TO "prisma";
|
||||
CREATE TABLE IF NOT EXISTS "public"."cities" (
|
||||
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
|
||||
"geographic_id" "uuid",
|
||||
"name" character varying(100) NOT NULL,
|
||||
"code" character varying(10) NOT NULL,
|
||||
"created_at" timestamp(6) with time zone DEFAULT "now"() NOT NULL,
|
||||
"updated_at" timestamp(6) with time zone DEFAULT "now"() NOT NULL
|
||||
);
|
||||
ALTER TABLE "public"."cities" OWNER TO "prisma";
|
||||
CREATE TABLE IF NOT EXISTS "public"."contact_messages" (
|
||||
"name" character varying(255),
|
||||
"email" character varying(255),
|
||||
"phone" character varying(20),
|
||||
"message_type" character varying(50),
|
||||
"message_type_label" character varying(50),
|
||||
"message" "text",
|
||||
"status" "public"."status_contact_messages" DEFAULT 'new'::"public"."status_contact_messages" NOT NULL,
|
||||
"created_at" timestamp(6) with time zone DEFAULT "now"() NOT NULL,
|
||||
"updated_at" timestamp(6) with time zone NOT NULL,
|
||||
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL
|
||||
);
|
||||
ALTER TABLE "public"."contact_messages" OWNER TO "prisma";
|
||||
CREATE TABLE IF NOT EXISTS "public"."crime_cases" (
|
||||
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
|
||||
"crime_id" "uuid",
|
||||
"crime_category_id" "uuid",
|
||||
"date" timestamp(6) with time zone NOT NULL,
|
||||
"time" timestamp(6) with time zone NOT NULL,
|
||||
"location" character varying(255) NOT NULL,
|
||||
"latitude" double precision NOT NULL,
|
||||
"longitude" double precision NOT NULL,
|
||||
"description" "text" NOT NULL,
|
||||
"victim_count" integer NOT NULL,
|
||||
"status" "public"."crime_status" DEFAULT 'new'::"public"."crime_status" NOT NULL,
|
||||
"created_at" timestamp(6) with time zone DEFAULT "now"() NOT NULL,
|
||||
"updated_at" timestamp(6) with time zone DEFAULT "now"() NOT NULL
|
||||
);
|
||||
ALTER TABLE "public"."crime_cases" OWNER TO "prisma";
|
||||
CREATE TABLE IF NOT EXISTS "public"."crime_categories" (
|
||||
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
|
||||
"name" character varying(255) NOT NULL,
|
||||
"description" "text" NOT NULL,
|
||||
"created_at" timestamp(6) with time zone DEFAULT "now"() NOT NULL,
|
||||
"updated_at" timestamp(6) with time zone DEFAULT "now"() NOT NULL
|
||||
);
|
||||
ALTER TABLE "public"."crime_categories" OWNER TO "prisma";
|
||||
CREATE TABLE IF NOT EXISTS "public"."crimes" (
|
||||
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
|
||||
"district_id" "uuid",
|
||||
"city_id" "uuid",
|
||||
"year" integer NOT NULL,
|
||||
"number_of_crime" integer NOT NULL,
|
||||
"rate" "public"."crime_rates" DEFAULT 'low'::"public"."crime_rates" NOT NULL,
|
||||
"heat_map" "jsonb",
|
||||
"created_at" timestamp(6) with time zone DEFAULT "now"() NOT NULL,
|
||||
"updated_at" timestamp(6) with time zone DEFAULT "now"() NOT NULL
|
||||
);
|
||||
ALTER TABLE "public"."crimes" OWNER TO "prisma";
|
||||
CREATE TABLE IF NOT EXISTS "public"."demographics" (
|
||||
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
|
||||
"district_id" "uuid",
|
||||
"city_id" "uuid",
|
||||
"province_id" "uuid",
|
||||
"year" integer NOT NULL,
|
||||
"population" integer NOT NULL,
|
||||
"population_density" double precision NOT NULL,
|
||||
"poverty_rate" double precision NOT NULL,
|
||||
"created_at" timestamp(6) with time zone DEFAULT "now"() NOT NULL,
|
||||
"updated_at" timestamp(6) with time zone DEFAULT "now"() NOT NULL
|
||||
);
|
||||
ALTER TABLE "public"."demographics" OWNER TO "prisma";
|
||||
CREATE TABLE IF NOT EXISTS "public"."districts" (
|
||||
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
|
||||
"city_id" "uuid" NOT NULL,
|
||||
"name" character varying(100) NOT NULL,
|
||||
"code" character varying(10) NOT NULL,
|
||||
"created_at" timestamp(6) with time zone DEFAULT "now"() NOT NULL,
|
||||
"updated_at" timestamp(6) with time zone DEFAULT "now"() NOT NULL
|
||||
);
|
||||
ALTER TABLE "public"."districts" OWNER TO "prisma";
|
||||
CREATE TABLE IF NOT EXISTS "public"."geographics" (
|
||||
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
|
||||
"district_id" "uuid",
|
||||
"latitude" double precision,
|
||||
"longitude" double precision,
|
||||
"land_area" double precision,
|
||||
"polygon" "jsonb",
|
||||
"created_at" timestamp(6) with time zone DEFAULT "now"() NOT NULL,
|
||||
"updated_at" timestamp(6) with time zone DEFAULT "now"() NOT NULL
|
||||
);
|
||||
ALTER TABLE "public"."geographics" OWNER TO "prisma";
|
||||
ALTER TABLE ONLY "public"."_prisma_migrations"
|
||||
ADD CONSTRAINT "_prisma_migrations_pkey" PRIMARY KEY ("id");
|
||||
ALTER TABLE ONLY "public"."cities"
|
||||
ADD CONSTRAINT "cities_pkey" PRIMARY KEY ("id");
|
||||
ALTER TABLE ONLY "public"."contact_messages"
|
||||
ADD CONSTRAINT "contact_messages_pkey" PRIMARY KEY ("id");
|
||||
ALTER TABLE ONLY "public"."crime_cases"
|
||||
ADD CONSTRAINT "crime_cases_pkey" PRIMARY KEY ("id");
|
||||
ALTER TABLE ONLY "public"."crime_categories"
|
||||
ADD CONSTRAINT "crime_categories_pkey" PRIMARY KEY ("id");
|
||||
ALTER TABLE ONLY "public"."crimes"
|
||||
ADD CONSTRAINT "crimes_pkey" PRIMARY KEY ("id");
|
||||
ALTER TABLE ONLY "public"."demographics"
|
||||
ADD CONSTRAINT "demographics_pkey" PRIMARY KEY ("id");
|
||||
ALTER TABLE ONLY "public"."districts"
|
||||
ADD CONSTRAINT "districts_pkey" PRIMARY KEY ("id");
|
||||
ALTER TABLE ONLY "public"."geographics"
|
||||
ADD CONSTRAINT "geographics_pkey" PRIMARY KEY ("id");
|
||||
CREATE INDEX "cities_name_idx" ON "public"."cities" USING "btree" ("name");
|
||||
CREATE UNIQUE INDEX "crimes_city_id_year_key" ON "public"."crimes" USING "btree" ("city_id", "year");
|
||||
CREATE UNIQUE INDEX "crimes_district_id_year_key" ON "public"."crimes" USING "btree" ("district_id", "year");
|
||||
CREATE UNIQUE INDEX "demographics_city_id_year_key" ON "public"."demographics" USING "btree" ("city_id", "year");
|
||||
CREATE UNIQUE INDEX "demographics_district_id_year_key" ON "public"."demographics" USING "btree" ("district_id", "year");
|
||||
CREATE INDEX "districts_name_idx" ON "public"."districts" USING "btree" ("name");
|
||||
CREATE UNIQUE INDEX "geographics_district_id_key" ON "public"."geographics" USING "btree" ("district_id");
|
||||
ALTER TABLE ONLY "public"."cities"
|
||||
ADD CONSTRAINT "cities_geographic_id_fkey" FOREIGN KEY ("geographic_id") REFERENCES "public"."geographics"("id") ON UPDATE CASCADE ON DELETE SET NULL;
|
||||
ALTER TABLE ONLY "public"."crime_cases"
|
||||
ADD CONSTRAINT "crime_cases_crime_category_id_fkey" FOREIGN KEY ("crime_category_id") REFERENCES "public"."crime_categories"("id") ON UPDATE CASCADE ON DELETE SET NULL;
|
||||
ALTER TABLE ONLY "public"."crime_cases"
|
||||
ADD CONSTRAINT "crime_cases_crime_id_fkey" FOREIGN KEY ("crime_id") REFERENCES "public"."crimes"("id") ON UPDATE CASCADE ON DELETE SET NULL;
|
||||
ALTER TABLE ONLY "public"."crimes"
|
||||
ADD CONSTRAINT "crimes_city_id_fkey" FOREIGN KEY ("city_id") REFERENCES "public"."cities"("id") ON UPDATE CASCADE ON DELETE SET NULL;
|
||||
ALTER TABLE ONLY "public"."crimes"
|
||||
ADD CONSTRAINT "crimes_district_id_fkey" FOREIGN KEY ("district_id") REFERENCES "public"."districts"("id") ON UPDATE CASCADE ON DELETE SET NULL;
|
||||
ALTER TABLE ONLY "public"."demographics"
|
||||
ADD CONSTRAINT "demographics_city_id_fkey" FOREIGN KEY ("city_id") REFERENCES "public"."cities"("id") ON UPDATE CASCADE ON DELETE SET NULL;
|
||||
ALTER TABLE ONLY "public"."demographics"
|
||||
ADD CONSTRAINT "demographics_district_id_fkey" FOREIGN KEY ("district_id") REFERENCES "public"."districts"("id") ON UPDATE CASCADE ON DELETE SET NULL;
|
||||
ALTER TABLE ONLY "public"."districts"
|
||||
ADD CONSTRAINT "districts_city_id_fkey" FOREIGN KEY ("city_id") REFERENCES "public"."cities"("id") ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
ALTER TABLE ONLY "public"."geographics"
|
||||
ADD CONSTRAINT "geographics_district_id_fkey" FOREIGN KEY ("district_id") REFERENCES "public"."districts"("id") ON UPDATE CASCADE ON DELETE SET NULL;
|
||||
ALTER PUBLICATION "supabase_realtime" OWNER TO "postgres";
|
||||
GRANT USAGE ON SCHEMA "public" TO "postgres";
|
||||
GRANT USAGE ON SCHEMA "public" TO "anon";
|
||||
GRANT USAGE ON SCHEMA "public" TO "authenticated";
|
||||
GRANT USAGE ON SCHEMA "public" TO "service_role";
|
||||
GRANT ALL ON SCHEMA "public" TO "prisma";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "postgres";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "anon";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "authenticated";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "service_role";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "prisma";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "postgres";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "anon";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "authenticated";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "service_role";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "prisma";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "postgres";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "anon";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "authenticated";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "service_role";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "prisma";
|
||||
RESET ALL;
|
|
@ -0,0 +1,189 @@
|
|||
revoke delete on table "public"."_prisma_migrations" from "anon";
|
||||
revoke insert on table "public"."_prisma_migrations" from "anon";
|
||||
revoke references on table "public"."_prisma_migrations" from "anon";
|
||||
revoke select on table "public"."_prisma_migrations" from "anon";
|
||||
revoke trigger on table "public"."_prisma_migrations" from "anon";
|
||||
revoke truncate on table "public"."_prisma_migrations" from "anon";
|
||||
revoke update on table "public"."_prisma_migrations" from "anon";
|
||||
revoke delete on table "public"."_prisma_migrations" from "authenticated";
|
||||
revoke insert on table "public"."_prisma_migrations" from "authenticated";
|
||||
revoke references on table "public"."_prisma_migrations" from "authenticated";
|
||||
revoke select on table "public"."_prisma_migrations" from "authenticated";
|
||||
revoke trigger on table "public"."_prisma_migrations" from "authenticated";
|
||||
revoke truncate on table "public"."_prisma_migrations" from "authenticated";
|
||||
revoke update on table "public"."_prisma_migrations" from "authenticated";
|
||||
revoke delete on table "public"."_prisma_migrations" from "service_role";
|
||||
revoke insert on table "public"."_prisma_migrations" from "service_role";
|
||||
revoke references on table "public"."_prisma_migrations" from "service_role";
|
||||
revoke select on table "public"."_prisma_migrations" from "service_role";
|
||||
revoke trigger on table "public"."_prisma_migrations" from "service_role";
|
||||
revoke truncate on table "public"."_prisma_migrations" from "service_role";
|
||||
revoke update on table "public"."_prisma_migrations" from "service_role";
|
||||
revoke delete on table "public"."cities" from "anon";
|
||||
revoke insert on table "public"."cities" from "anon";
|
||||
revoke references on table "public"."cities" from "anon";
|
||||
revoke select on table "public"."cities" from "anon";
|
||||
revoke trigger on table "public"."cities" from "anon";
|
||||
revoke truncate on table "public"."cities" from "anon";
|
||||
revoke update on table "public"."cities" from "anon";
|
||||
revoke delete on table "public"."cities" from "authenticated";
|
||||
revoke insert on table "public"."cities" from "authenticated";
|
||||
revoke references on table "public"."cities" from "authenticated";
|
||||
revoke select on table "public"."cities" from "authenticated";
|
||||
revoke trigger on table "public"."cities" from "authenticated";
|
||||
revoke truncate on table "public"."cities" from "authenticated";
|
||||
revoke update on table "public"."cities" from "authenticated";
|
||||
revoke delete on table "public"."cities" from "service_role";
|
||||
revoke insert on table "public"."cities" from "service_role";
|
||||
revoke references on table "public"."cities" from "service_role";
|
||||
revoke select on table "public"."cities" from "service_role";
|
||||
revoke trigger on table "public"."cities" from "service_role";
|
||||
revoke truncate on table "public"."cities" from "service_role";
|
||||
revoke update on table "public"."cities" from "service_role";
|
||||
revoke delete on table "public"."contact_messages" from "anon";
|
||||
revoke insert on table "public"."contact_messages" from "anon";
|
||||
revoke references on table "public"."contact_messages" from "anon";
|
||||
revoke select on table "public"."contact_messages" from "anon";
|
||||
revoke trigger on table "public"."contact_messages" from "anon";
|
||||
revoke truncate on table "public"."contact_messages" from "anon";
|
||||
revoke update on table "public"."contact_messages" from "anon";
|
||||
revoke delete on table "public"."contact_messages" from "authenticated";
|
||||
revoke insert on table "public"."contact_messages" from "authenticated";
|
||||
revoke references on table "public"."contact_messages" from "authenticated";
|
||||
revoke select on table "public"."contact_messages" from "authenticated";
|
||||
revoke trigger on table "public"."contact_messages" from "authenticated";
|
||||
revoke truncate on table "public"."contact_messages" from "authenticated";
|
||||
revoke update on table "public"."contact_messages" from "authenticated";
|
||||
revoke delete on table "public"."contact_messages" from "service_role";
|
||||
revoke insert on table "public"."contact_messages" from "service_role";
|
||||
revoke references on table "public"."contact_messages" from "service_role";
|
||||
revoke select on table "public"."contact_messages" from "service_role";
|
||||
revoke trigger on table "public"."contact_messages" from "service_role";
|
||||
revoke truncate on table "public"."contact_messages" from "service_role";
|
||||
revoke update on table "public"."contact_messages" from "service_role";
|
||||
revoke delete on table "public"."crime_cases" from "anon";
|
||||
revoke insert on table "public"."crime_cases" from "anon";
|
||||
revoke references on table "public"."crime_cases" from "anon";
|
||||
revoke select on table "public"."crime_cases" from "anon";
|
||||
revoke trigger on table "public"."crime_cases" from "anon";
|
||||
revoke truncate on table "public"."crime_cases" from "anon";
|
||||
revoke update on table "public"."crime_cases" from "anon";
|
||||
revoke delete on table "public"."crime_cases" from "authenticated";
|
||||
revoke insert on table "public"."crime_cases" from "authenticated";
|
||||
revoke references on table "public"."crime_cases" from "authenticated";
|
||||
revoke select on table "public"."crime_cases" from "authenticated";
|
||||
revoke trigger on table "public"."crime_cases" from "authenticated";
|
||||
revoke truncate on table "public"."crime_cases" from "authenticated";
|
||||
revoke update on table "public"."crime_cases" from "authenticated";
|
||||
revoke delete on table "public"."crime_cases" from "service_role";
|
||||
revoke insert on table "public"."crime_cases" from "service_role";
|
||||
revoke references on table "public"."crime_cases" from "service_role";
|
||||
revoke select on table "public"."crime_cases" from "service_role";
|
||||
revoke trigger on table "public"."crime_cases" from "service_role";
|
||||
revoke truncate on table "public"."crime_cases" from "service_role";
|
||||
revoke update on table "public"."crime_cases" from "service_role";
|
||||
revoke delete on table "public"."crime_categories" from "anon";
|
||||
revoke insert on table "public"."crime_categories" from "anon";
|
||||
revoke references on table "public"."crime_categories" from "anon";
|
||||
revoke select on table "public"."crime_categories" from "anon";
|
||||
revoke trigger on table "public"."crime_categories" from "anon";
|
||||
revoke truncate on table "public"."crime_categories" from "anon";
|
||||
revoke update on table "public"."crime_categories" from "anon";
|
||||
revoke delete on table "public"."crime_categories" from "authenticated";
|
||||
revoke insert on table "public"."crime_categories" from "authenticated";
|
||||
revoke references on table "public"."crime_categories" from "authenticated";
|
||||
revoke select on table "public"."crime_categories" from "authenticated";
|
||||
revoke trigger on table "public"."crime_categories" from "authenticated";
|
||||
revoke truncate on table "public"."crime_categories" from "authenticated";
|
||||
revoke update on table "public"."crime_categories" from "authenticated";
|
||||
revoke delete on table "public"."crime_categories" from "service_role";
|
||||
revoke insert on table "public"."crime_categories" from "service_role";
|
||||
revoke references on table "public"."crime_categories" from "service_role";
|
||||
revoke select on table "public"."crime_categories" from "service_role";
|
||||
revoke trigger on table "public"."crime_categories" from "service_role";
|
||||
revoke truncate on table "public"."crime_categories" from "service_role";
|
||||
revoke update on table "public"."crime_categories" from "service_role";
|
||||
revoke delete on table "public"."crimes" from "anon";
|
||||
revoke insert on table "public"."crimes" from "anon";
|
||||
revoke references on table "public"."crimes" from "anon";
|
||||
revoke select on table "public"."crimes" from "anon";
|
||||
revoke trigger on table "public"."crimes" from "anon";
|
||||
revoke truncate on table "public"."crimes" from "anon";
|
||||
revoke update on table "public"."crimes" from "anon";
|
||||
revoke delete on table "public"."crimes" from "authenticated";
|
||||
revoke insert on table "public"."crimes" from "authenticated";
|
||||
revoke references on table "public"."crimes" from "authenticated";
|
||||
revoke select on table "public"."crimes" from "authenticated";
|
||||
revoke trigger on table "public"."crimes" from "authenticated";
|
||||
revoke truncate on table "public"."crimes" from "authenticated";
|
||||
revoke update on table "public"."crimes" from "authenticated";
|
||||
revoke delete on table "public"."crimes" from "service_role";
|
||||
revoke insert on table "public"."crimes" from "service_role";
|
||||
revoke references on table "public"."crimes" from "service_role";
|
||||
revoke select on table "public"."crimes" from "service_role";
|
||||
revoke trigger on table "public"."crimes" from "service_role";
|
||||
revoke truncate on table "public"."crimes" from "service_role";
|
||||
revoke update on table "public"."crimes" from "service_role";
|
||||
revoke delete on table "public"."demographics" from "anon";
|
||||
revoke insert on table "public"."demographics" from "anon";
|
||||
revoke references on table "public"."demographics" from "anon";
|
||||
revoke select on table "public"."demographics" from "anon";
|
||||
revoke trigger on table "public"."demographics" from "anon";
|
||||
revoke truncate on table "public"."demographics" from "anon";
|
||||
revoke update on table "public"."demographics" from "anon";
|
||||
revoke delete on table "public"."demographics" from "authenticated";
|
||||
revoke insert on table "public"."demographics" from "authenticated";
|
||||
revoke references on table "public"."demographics" from "authenticated";
|
||||
revoke select on table "public"."demographics" from "authenticated";
|
||||
revoke trigger on table "public"."demographics" from "authenticated";
|
||||
revoke truncate on table "public"."demographics" from "authenticated";
|
||||
revoke update on table "public"."demographics" from "authenticated";
|
||||
revoke delete on table "public"."demographics" from "service_role";
|
||||
revoke insert on table "public"."demographics" from "service_role";
|
||||
revoke references on table "public"."demographics" from "service_role";
|
||||
revoke select on table "public"."demographics" from "service_role";
|
||||
revoke trigger on table "public"."demographics" from "service_role";
|
||||
revoke truncate on table "public"."demographics" from "service_role";
|
||||
revoke update on table "public"."demographics" from "service_role";
|
||||
revoke delete on table "public"."districts" from "anon";
|
||||
revoke insert on table "public"."districts" from "anon";
|
||||
revoke references on table "public"."districts" from "anon";
|
||||
revoke select on table "public"."districts" from "anon";
|
||||
revoke trigger on table "public"."districts" from "anon";
|
||||
revoke truncate on table "public"."districts" from "anon";
|
||||
revoke update on table "public"."districts" from "anon";
|
||||
revoke delete on table "public"."districts" from "authenticated";
|
||||
revoke insert on table "public"."districts" from "authenticated";
|
||||
revoke references on table "public"."districts" from "authenticated";
|
||||
revoke select on table "public"."districts" from "authenticated";
|
||||
revoke trigger on table "public"."districts" from "authenticated";
|
||||
revoke truncate on table "public"."districts" from "authenticated";
|
||||
revoke update on table "public"."districts" from "authenticated";
|
||||
revoke delete on table "public"."districts" from "service_role";
|
||||
revoke insert on table "public"."districts" from "service_role";
|
||||
revoke references on table "public"."districts" from "service_role";
|
||||
revoke select on table "public"."districts" from "service_role";
|
||||
revoke trigger on table "public"."districts" from "service_role";
|
||||
revoke truncate on table "public"."districts" from "service_role";
|
||||
revoke update on table "public"."districts" from "service_role";
|
||||
revoke delete on table "public"."geographics" from "anon";
|
||||
revoke insert on table "public"."geographics" from "anon";
|
||||
revoke references on table "public"."geographics" from "anon";
|
||||
revoke select on table "public"."geographics" from "anon";
|
||||
revoke trigger on table "public"."geographics" from "anon";
|
||||
revoke truncate on table "public"."geographics" from "anon";
|
||||
revoke update on table "public"."geographics" from "anon";
|
||||
revoke delete on table "public"."geographics" from "authenticated";
|
||||
revoke insert on table "public"."geographics" from "authenticated";
|
||||
revoke references on table "public"."geographics" from "authenticated";
|
||||
revoke select on table "public"."geographics" from "authenticated";
|
||||
revoke trigger on table "public"."geographics" from "authenticated";
|
||||
revoke truncate on table "public"."geographics" from "authenticated";
|
||||
revoke update on table "public"."geographics" from "authenticated";
|
||||
revoke delete on table "public"."geographics" from "service_role";
|
||||
revoke insert on table "public"."geographics" from "service_role";
|
||||
revoke references on table "public"."geographics" from "service_role";
|
||||
revoke select on table "public"."geographics" from "service_role";
|
||||
revoke trigger on table "public"."geographics" from "service_role";
|
||||
revoke truncate on table "public"."geographics" from "service_role";
|
||||
revoke update on table "public"."geographics" from "service_role";
|
|
@ -0,0 +1,55 @@
|
|||
-- Create a table for public profiles
|
||||
create table profiles (
|
||||
id uuid references auth.users not null primary key,
|
||||
updated_at timestamp with time zone,
|
||||
username text unique,
|
||||
full_name text,
|
||||
avatar_url text,
|
||||
website text,
|
||||
|
||||
constraint username_length check (char_length(username) >= 3)
|
||||
);
|
||||
-- Set up Row Level Security (RLS)
|
||||
-- See https://supabase.com/docs/guides/database/postgres/row-level-security for more details.
|
||||
alter table profiles
|
||||
enable row level security;
|
||||
|
||||
create policy "Public profiles are viewable by everyone." on profiles
|
||||
for select using (true);
|
||||
|
||||
create policy "Users can insert their own profile." on profiles
|
||||
for insert with check ((select auth.uid()) = id);
|
||||
|
||||
create policy "Users can update own profile." on profiles
|
||||
for update using ((select auth.uid()) = id);
|
||||
|
||||
-- This trigger automatically creates a profile entry when a new user signs up via Supabase Auth.
|
||||
-- See https://supabase.com/docs/guides/auth/managing-user-data#using-triggers for more details.
|
||||
create function public.handle_new_user()
|
||||
returns trigger
|
||||
set search_path = ''
|
||||
as $$
|
||||
begin
|
||||
insert into public.profiles (id, full_name, avatar_url)
|
||||
values (new.id, new.raw_user_meta_data->>'full_name', new.raw_user_meta_data->>'avatar_url');
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql security definer;
|
||||
create trigger on_auth_user_created
|
||||
after insert on auth.users
|
||||
for each row execute procedure public.handle_new_user();
|
||||
|
||||
-- Set up Storage!
|
||||
insert into storage.buckets (id, name)
|
||||
values ('avatars', 'avatars');
|
||||
|
||||
-- Set up access controls for storage.
|
||||
-- See https://supabase.com/docs/guides/storage/security/access-control#policy-examples for more details.
|
||||
create policy "Avatar images are publicly accessible." on storage.objects
|
||||
for select using (bucket_id = 'avatars');
|
||||
|
||||
create policy "Anyone can upload an avatar." on storage.objects
|
||||
for insert with check (bucket_id = 'avatars');
|
||||
|
||||
create policy "Anyone can update their own avatar." on storage.objects
|
||||
for update using ((select auth.uid()) = owner) with check (bucket_id = 'avatars');
|
Loading…
Reference in New Issue