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.lint": true,
|
||||||
"deno.unstable": [
|
"deno.unstable": [
|
||||||
"bare-node-builtins",
|
"bare-node-builtins",
|
||||||
|
@ -15,8 +20,5 @@
|
||||||
"fs",
|
"fs",
|
||||||
"http",
|
"http",
|
||||||
"net"
|
"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,
|
SidebarRail,
|
||||||
} from "@/app/_components/ui/sidebar";
|
} from "@/app/_components/ui/sidebar";
|
||||||
import { NavPreMain } from "./navigations/nav-pre-main";
|
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>) {
|
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||||
return (
|
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";
|
"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 {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/app/_components/ui/form";
|
} from "@/app/_components/ui/form";
|
||||||
import { Input } from "@/app/_components/ui/input";
|
import { Input } from "@/app/_components/ui/input";
|
||||||
|
import { Button } from "@/app/_components/ui/button";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
|
@ -16,175 +34,448 @@ import {
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/app/_components/ui/select";
|
} from "@/app/_components/ui/select";
|
||||||
|
import { Textarea } from "@/app/_components/ui/textarea";
|
||||||
import {
|
import {
|
||||||
Sheet,
|
Avatar,
|
||||||
SheetContent,
|
AvatarFallback,
|
||||||
SheetDescription,
|
AvatarImage,
|
||||||
SheetHeader,
|
} from "@/app/_components/ui/avatar";
|
||||||
SheetTitle,
|
import { Calendar } from "@/app/_components/ui/calendar";
|
||||||
} from "@/app/_components/ui/sheet";
|
import {
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
Popover,
|
||||||
import { useForm } from "react-hook-form";
|
PopoverContent,
|
||||||
import * as z from "zod";
|
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";
|
// Create a schema for form validation
|
||||||
import { useCallback } from "react";
|
const userFormSchema = z.object({
|
||||||
|
// User fields
|
||||||
const formSchema = z.object({
|
email: z.string().email({ message: "Please enter a valid email address" }),
|
||||||
firstName: z.string().min(2, {
|
firstName: z.string().min(1, { message: "First name is required" }),
|
||||||
message: "First name must be at least 2 characters.",
|
lastName: z.string().min(1, { message: "Last name is required" }),
|
||||||
}),
|
avatar: z.string().optional(),
|
||||||
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.",
|
|
||||||
}),
|
|
||||||
role: z.enum(["admin", "staff", "user"]),
|
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 {
|
interface EditUserSheetProps {
|
||||||
isOpen: boolean;
|
open: boolean;
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
selectedUser: User | null;
|
user?: UserWithProfile | null;
|
||||||
onUserUpdate: (updatedUser: User) => void;
|
onSave: (user: UserWithProfile) => void;
|
||||||
|
isNew?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EditUserSheet({
|
export function EditUserSheet({
|
||||||
isOpen,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
selectedUser,
|
user,
|
||||||
onUserUpdate,
|
onSave,
|
||||||
|
isNew = false,
|
||||||
}: EditUserSheetProps) {
|
}: EditUserSheetProps) {
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<UserFormValues>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(userFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
firstName: selectedUser?.firstName || "",
|
email: "",
|
||||||
lastName: selectedUser?.lastName || "",
|
firstName: "",
|
||||||
email: selectedUser?.email || "",
|
lastName: "",
|
||||||
role: selectedUser?.role || "user",
|
avatar: "/placeholder.svg?height=40&width=40",
|
||||||
status: selectedUser?.status || "inactive",
|
role: "user",
|
||||||
|
password: isNew ? "" : undefined,
|
||||||
|
bio: "",
|
||||||
|
phone: "",
|
||||||
|
address: "",
|
||||||
|
city: "",
|
||||||
|
country: "",
|
||||||
|
birthDate: undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = useCallback(
|
// Update form when user changes
|
||||||
(values: z.infer<typeof formSchema>) => {
|
useEffect(() => {
|
||||||
if (!selectedUser) return;
|
if (user) {
|
||||||
|
const birthDate = user.profile?.birthDate
|
||||||
|
? new Date(user.profile.birthDate)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const updatedUser: User = {
|
form.reset({
|
||||||
...selectedUser,
|
email: user.email,
|
||||||
firstName: values.firstName,
|
firstName: user.firstName,
|
||||||
lastName: values.lastName,
|
lastName: user.lastName,
|
||||||
email: values.email,
|
avatar: user.avatar,
|
||||||
role: values.role,
|
role: user.role,
|
||||||
status: values.status,
|
// 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]);
|
||||||
|
|
||||||
onUserUpdate(updatedUser);
|
const onSubmit = (data: UserFormValues) => {
|
||||||
onOpenChange(false);
|
// Prepare the user object with profile
|
||||||
},
|
const updatedUser: UserWithProfile = {
|
||||||
[selectedUser, onUserUpdate, onOpenChange]
|
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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save the user
|
||||||
|
onSave(updatedUser);
|
||||||
|
|
||||||
|
// Close the sheet
|
||||||
|
onOpenChange(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sheet open={isOpen} onOpenChange={onOpenChange}>
|
<Sheet open={open} onOpenChange={onOpenChange}>
|
||||||
<SheetContent side="right">
|
<SheetContent className="sm:max-w-xl overflow-y-auto">
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle>Edit User</SheetTitle>
|
<SheetTitle>{isNew ? "Add New User" : "Edit User"}</SheetTitle>
|
||||||
<SheetDescription>
|
<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>
|
</SheetDescription>
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<Form {...form}>
|
<div className="py-4">
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
<Tabs defaultValue="user" className="w-full">
|
||||||
<FormField
|
<TabsList className="grid w-full grid-cols-2">
|
||||||
control={form.control}
|
<TabsTrigger value="user">User Details</TabsTrigger>
|
||||||
name="firstName"
|
<TabsTrigger value="profile">Profile Information</TabsTrigger>
|
||||||
render={({ field }) => (
|
</TabsList>
|
||||||
<FormItem>
|
<Form {...form}>
|
||||||
<FormLabel>First name</FormLabel>
|
<form
|
||||||
<FormControl>
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
<Input placeholder="First name" {...field} />
|
className="space-y-6 pt-6"
|
||||||
</FormControl>
|
>
|
||||||
<FormMessage />
|
<TabsContent value="user" className="space-y-4">
|
||||||
</FormItem>
|
<div className="flex justify-center mb-6">
|
||||||
)}
|
<Avatar className="h-24 w-24">
|
||||||
/>
|
<AvatarImage
|
||||||
<FormField
|
src={
|
||||||
control={form.control}
|
form.watch("avatar") ||
|
||||||
name="lastName"
|
"/placeholder.svg?height=96&width=96"
|
||||||
render={({ field }) => (
|
}
|
||||||
<FormItem>
|
alt="User avatar"
|
||||||
<FormLabel>Last name</FormLabel>
|
/>
|
||||||
<FormControl>
|
<AvatarFallback>
|
||||||
<Input placeholder="Last name" {...field} />
|
{form.watch("firstName")?.charAt(0) || ""}
|
||||||
</FormControl>
|
{form.watch("lastName")?.charAt(0) || ""}
|
||||||
<FormMessage />
|
</AvatarFallback>
|
||||||
</FormItem>
|
</Avatar>
|
||||||
)}
|
</div>
|
||||||
/>
|
|
||||||
<FormField
|
<div className="grid grid-cols-2 gap-4">
|
||||||
control={form.control}
|
<FormField
|
||||||
name="email"
|
control={form.control}
|
||||||
render={({ field }) => (
|
name="firstName"
|
||||||
<FormItem>
|
render={({ field }) => (
|
||||||
<FormLabel>Email</FormLabel>
|
<FormItem>
|
||||||
<FormControl>
|
<FormLabel>First Name</FormLabel>
|
||||||
<Input placeholder="Email" {...field} />
|
<FormControl>
|
||||||
</FormControl>
|
<Input placeholder="First name" {...field} />
|
||||||
<FormMessage />
|
</FormControl>
|
||||||
</FormItem>
|
<FormMessage />
|
||||||
)}
|
</FormItem>
|
||||||
/>
|
)}
|
||||||
<FormField
|
/>
|
||||||
control={form.control}
|
<FormField
|
||||||
name="role"
|
control={form.control}
|
||||||
render={({ field }) => (
|
name="lastName"
|
||||||
<FormItem>
|
render={({ field }) => (
|
||||||
<FormLabel>Role</FormLabel>
|
<FormItem>
|
||||||
<Select
|
<FormLabel>Last Name</FormLabel>
|
||||||
onValueChange={field.onChange}
|
<FormControl>
|
||||||
defaultValue={field.value}
|
<Input placeholder="Last name" {...field} />
|
||||||
>
|
</FormControl>
|
||||||
<SelectTrigger className="w-[180px]">
|
<FormMessage />
|
||||||
<SelectValue placeholder="Select a role" />
|
</FormItem>
|
||||||
</SelectTrigger>
|
)}
|
||||||
<SelectContent>
|
/>
|
||||||
<SelectItem value="admin">Admin</SelectItem>
|
</div>
|
||||||
<SelectItem value="staff">Staff</SelectItem>
|
|
||||||
<SelectItem value="user">User</SelectItem>
|
<FormField
|
||||||
</SelectContent>
|
control={form.control}
|
||||||
</Select>
|
name="email"
|
||||||
<FormMessage />
|
render={({ field }) => (
|
||||||
</FormItem>
|
<FormItem>
|
||||||
)}
|
<FormLabel>Email</FormLabel>
|
||||||
/>
|
<FormControl>
|
||||||
<FormField
|
<Input
|
||||||
control={form.control}
|
placeholder="Email address"
|
||||||
name="status"
|
type="email"
|
||||||
render={({ field }) => (
|
{...field}
|
||||||
<FormItem>
|
/>
|
||||||
<FormLabel>Status</FormLabel>
|
</FormControl>
|
||||||
<Select
|
<FormMessage />
|
||||||
onValueChange={field.onChange}
|
</FormItem>
|
||||||
defaultValue={field.value}
|
)}
|
||||||
>
|
/>
|
||||||
<SelectTrigger className="w-[180px]">
|
|
||||||
<SelectValue placeholder="Select a status" />
|
{isNew && (
|
||||||
</SelectTrigger>
|
<FormField
|
||||||
<SelectContent>
|
control={form.control}
|
||||||
<SelectItem value="active">Active</SelectItem>
|
name="password"
|
||||||
<SelectItem value="inactive">Inactive</SelectItem>
|
render={({ field }) => (
|
||||||
</SelectContent>
|
<FormItem>
|
||||||
</Select>
|
<FormLabel>Password</FormLabel>
|
||||||
<FormMessage />
|
<FormControl>
|
||||||
</FormItem>
|
<Input
|
||||||
)}
|
placeholder="Set password"
|
||||||
/>
|
type="password"
|
||||||
<Button type="submit">Save changes</Button>
|
{...field}
|
||||||
</form>
|
/>
|
||||||
</Form>
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
Minimum 8 characters
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="role"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Role</FormLabel>
|
||||||
|
<Select
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
defaultValue={field.value}
|
||||||
|
value={field.value}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a role" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="admin">Admin</SelectItem>
|
||||||
|
<SelectItem value="staff">Staff</SelectItem>
|
||||||
|
<SelectItem value="user">User</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="avatar"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Avatar URL</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="Avatar URL" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</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>
|
</SheetContent>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
);
|
);
|
||||||
|
|
|
@ -183,15 +183,15 @@ export function UsersTable() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (searchValue) {
|
if (searchValue) {
|
||||||
setColumnFilters((prev) => {
|
setColumnFilters((prev) => {
|
||||||
// Remove existing kolomusers filter if it exists
|
// Remove existing usersColoumn filter if it exists
|
||||||
const filtered = prev.filter((filter) => filter.id !== "kolomusers");
|
const filtered = prev.filter((filter) => filter.id !== "usersColoumn");
|
||||||
// Add the new search filter
|
// Add the new search filter
|
||||||
return [...filtered, { id: "kolomusers", value: searchValue }];
|
return [...filtered, { id: "usersColoumn", value: searchValue }];
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Remove the filter if search is empty
|
// Remove the filter if search is empty
|
||||||
setColumnFilters((prev) =>
|
setColumnFilters((prev) =>
|
||||||
prev.filter((filter) => filter.id !== "kolomusers")
|
prev.filter((filter) => filter.id !== "usersColoumn")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [searchValue]);
|
}, [searchValue]);
|
||||||
|
@ -256,7 +256,7 @@ export function UsersTable() {
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "kolomusers",
|
accessorKey: "usersColoumn",
|
||||||
header: ({ column }) => {
|
header: ({ column }) => {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
@ -575,11 +575,11 @@ export function UsersTable() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<EditUserSheet
|
<EditUserSheet
|
||||||
isOpen={isSheetOpen}
|
open={isSheetOpen}
|
||||||
onOpenChange={setIsSheetOpen}
|
onOpenChange={() => setIsSheetOpen(false)}
|
||||||
selectedUser={selectedUser}
|
user={selectedUser}
|
||||||
onUserUpdate={handleUserUpdate}
|
onSave={handleUserUpdate}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
"@radix-ui/react-separator": "^1.1.2",
|
"@radix-ui/react-separator": "^1.1.2",
|
||||||
"@radix-ui/react-slot": "^1.1.2",
|
"@radix-ui/react-slot": "^1.1.2",
|
||||||
"@radix-ui/react-switch": "^1.1.3",
|
"@radix-ui/react-switch": "^1.1.3",
|
||||||
|
"@radix-ui/react-tabs": "^1.1.3",
|
||||||
"@radix-ui/react-toast": "^1.2.6",
|
"@radix-ui/react-toast": "^1.2.6",
|
||||||
"@radix-ui/react-tooltip": "^1.1.8",
|
"@radix-ui/react-tooltip": "^1.1.8",
|
||||||
"@react-email/components": "0.0.33",
|
"@react-email/components": "0.0.33",
|
||||||
|
@ -33,7 +34,7 @@
|
||||||
"autoprefixer": "10.4.20",
|
"autoprefixer": "10.4.20",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^3.6.0",
|
||||||
"framer-motion": "^12.4.7",
|
"framer-motion": "^12.4.7",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "^0.468.0",
|
"lucide-react": "^0.468.0",
|
||||||
|
@ -43,6 +44,7 @@
|
||||||
"next-themes": "^0.4.3",
|
"next-themes": "^0.4.3",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
|
"react-day-picker": "^8.10.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.54.2",
|
||||||
"resend": "^4.1.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": {
|
"node_modules/@radix-ui/react-toast": {
|
||||||
"version": "1.2.6",
|
"version": "1.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.6.tgz",
|
||||||
|
@ -3910,9 +3942,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/date-fns": {
|
"node_modules/date-fns": {
|
||||||
"version": "4.1.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
|
||||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
|
@ -5863,6 +5895,20 @@
|
||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/react-dom": {
|
||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||||
|
@ -6906,9 +6952,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/supabase": {
|
"node_modules/supabase": {
|
||||||
"version": "2.12.1",
|
"version": "2.15.8",
|
||||||
"resolved": "https://registry.npmjs.org/supabase/-/supabase-2.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/supabase/-/supabase-2.15.8.tgz",
|
||||||
"integrity": "sha512-vB6LX1KGrqku8AFlp2vJw49IUB9g6Rz2b84qpcWSZ3mMDFumA6hDSbXbFJUnr3hcvyPzoOsQlhMTZN7a6o3hfA==",
|
"integrity": "sha512-yY4kVpdd7x9u5QqTW/8zUXIrMgdkBDGqQwkDugBLe8uoFdH9tVZKt0L5RmuM21RJ0MEQkby2sQrTfiXvgGyx9w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
"@radix-ui/react-separator": "^1.1.2",
|
"@radix-ui/react-separator": "^1.1.2",
|
||||||
"@radix-ui/react-slot": "^1.1.2",
|
"@radix-ui/react-slot": "^1.1.2",
|
||||||
"@radix-ui/react-switch": "^1.1.3",
|
"@radix-ui/react-switch": "^1.1.3",
|
||||||
|
"@radix-ui/react-tabs": "^1.1.3",
|
||||||
"@radix-ui/react-toast": "^1.2.6",
|
"@radix-ui/react-toast": "^1.2.6",
|
||||||
"@radix-ui/react-tooltip": "^1.1.8",
|
"@radix-ui/react-tooltip": "^1.1.8",
|
||||||
"@react-email/components": "0.0.33",
|
"@react-email/components": "0.0.33",
|
||||||
|
@ -34,7 +35,7 @@
|
||||||
"autoprefixer": "10.4.20",
|
"autoprefixer": "10.4.20",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^3.6.0",
|
||||||
"framer-motion": "^12.4.7",
|
"framer-motion": "^12.4.7",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "^0.468.0",
|
"lucide-react": "^0.468.0",
|
||||||
|
@ -44,6 +45,7 @@
|
||||||
"next-themes": "^0.4.3",
|
"next-themes": "^0.4.3",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
|
"react-day-picker": "^8.10.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.54.2",
|
||||||
"resend": "^4.1.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 @@
|
||||||
/*
|
-- CreateExtension
|
||||||
Warnings:
|
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.
|
-- CreateEnum
|
||||||
- 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.
|
CREATE TYPE "roles" AS ENUM ('admin', 'staff', 'user');
|
||||||
- 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 "status_contact_messages" AS ENUM ('new', 'read', 'replied', 'resolved');
|
||||||
|
|
||||||
*/
|
|
||||||
-- CreateEnum
|
-- CreateEnum
|
||||||
CREATE TYPE "crime_rates" AS ENUM ('low', 'medium', 'high');
|
CREATE TYPE "crime_rates" AS ENUM ('low', 'medium', 'high');
|
||||||
|
|
||||||
-- CreateEnum
|
-- CreateEnum
|
||||||
CREATE TYPE "crime_status" AS ENUM ('new', 'in_progress', 'resolved');
|
CREATE TYPE "crime_status" AS ENUM ('new', 'in_progress', 'resolved');
|
||||||
|
|
||||||
-- AlterTable
|
-- CreateTable
|
||||||
ALTER TABLE "contact_messages" ALTER COLUMN "created_at" SET DEFAULT now(),
|
CREATE TABLE "users" (
|
||||||
ALTER COLUMN "updated_at" DROP DEFAULT;
|
"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
|
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
|
||||||
ALTER TABLE "nav_items" ADD COLUMN "slug" VARCHAR(255) NOT NULL,
|
);
|
||||||
ALTER COLUMN "created_at" SET DEFAULT now(),
|
|
||||||
ALTER COLUMN "updated_at" DROP DEFAULT;
|
|
||||||
|
|
||||||
-- AlterTable
|
-- CreateTable
|
||||||
ALTER TABLE "nav_sub_items" ADD COLUMN "icon" VARCHAR(100) NOT NULL,
|
CREATE TABLE "profiles" (
|
||||||
ADD COLUMN "is_active" BOOLEAN NOT NULL DEFAULT false,
|
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||||
ADD COLUMN "slug" VARCHAR(255) NOT NULL,
|
"user_id" UUID NOT NULL,
|
||||||
ALTER COLUMN "created_at" SET DEFAULT now(),
|
"bio" TEXT,
|
||||||
ALTER COLUMN "updated_at" DROP DEFAULT;
|
"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
|
-- CreateTable
|
||||||
CREATE TABLE "cities" (
|
CREATE TABLE "cities" (
|
||||||
|
@ -127,6 +160,18 @@ CREATE TABLE "crime_categories" (
|
||||||
CONSTRAINT "crime_categories_pkey" PRIMARY KEY ("id")
|
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
|
-- CreateIndex
|
||||||
CREATE INDEX "cities_name_idx" ON "cities"("name");
|
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
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "crimes_city_id_year_key" ON "crimes"("city_id", "year");
|
CREATE UNIQUE INDEX "crimes_city_id_year_key" ON "crimes"("city_id", "year");
|
||||||
|
|
||||||
-- CreateIndex
|
-- AddForeignKey
|
||||||
CREATE INDEX "profiles_user_id_idx" ON "profiles"("user_id");
|
ALTER TABLE "profiles" ADD CONSTRAINT "profiles_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE "cities" ADD CONSTRAINT "cities_geographic_id_fkey" FOREIGN KEY ("geographic_id") REFERENCES "geographics"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
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 '[]';
|
|
@ -17,18 +17,36 @@ datasource db {
|
||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @db.Uuid
|
id String @id @db.Uuid
|
||||||
email String @unique @db.VarChar(255)
|
email String @unique @db.VarChar(255)
|
||||||
emailVerified Boolean @default(false) @map("email_verified")
|
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)
|
firstName String? @map("first_name") @db.VarChar(255)
|
||||||
lastName String? @map("last_name") @db.VarChar(255)
|
lastName String? @map("last_name") @db.VarChar(255)
|
||||||
avatar String? @db.VarChar(255)
|
avatar String? @db.VarChar(255)
|
||||||
role Role @default(user)
|
role Role @default(user)
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
updatedAt DateTime @updatedAt @map("updated_at")
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
lastSignedIn DateTime? @map("last_signed_in")
|
lastSignedIn DateTime? @map("last_sign_in_at")
|
||||||
metadata Json?
|
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?
|
profile Profile?
|
||||||
|
|
||||||
|
@ -52,16 +70,16 @@ model Profile {
|
||||||
}
|
}
|
||||||
|
|
||||||
model SupportRequest {
|
model SupportRequest {
|
||||||
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
name String? @db.VarChar(255)
|
name String? @db.VarChar(255)
|
||||||
email String? @db.VarChar(255)
|
email String? @db.VarChar(255)
|
||||||
phone String? @db.VarChar(20)
|
phone String? @db.VarChar(20)
|
||||||
messageType String? @map("message_type") @db.VarChar(50)
|
messageType String? @map("message_type") @db.VarChar(50)
|
||||||
messageTypeLabel String? @map("message_type_label") @db.VarChar(50)
|
messageTypeLabel String? @map("message_type_label") @db.VarChar(50)
|
||||||
message String? @db.Text
|
message String? @db.Text
|
||||||
status StatusSupportRequest @default(new)
|
status StatusSupportRequest @default(new)
|
||||||
createdAt DateTime @default(dbgenerated("now()")) @map("created_at") @db.Timestamptz(6)
|
createdAt DateTime @default(dbgenerated("now()")) @map("created_at") @db.Timestamptz(6)
|
||||||
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6)
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6)
|
||||||
|
|
||||||
@@map("contact_messages") // Maps to Supabase's 'contact_messages' table
|
@@map("contact_messages") // Maps to Supabase's 'contact_messages' table
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,14 @@ export const userSchema = z.object({
|
||||||
|
|
||||||
export type User = z.infer<typeof userSchema>;
|
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
|
export const createUserSchema = userSchema
|
||||||
.pick({
|
.pick({
|
||||||
id: true,
|
id: true,
|
||||||
|
|
|
@ -45,6 +45,11 @@ max_client_conn = 100
|
||||||
# [db.vault]
|
# [db.vault]
|
||||||
# secret_key = "env(SECRET_VALUE)"
|
# 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]
|
[db.seed]
|
||||||
# If enabled, seeds the database after migrations during a db reset.
|
# If enabled, seeds the database after migrations during a db reset.
|
||||||
enabled = true
|
enabled = true
|
||||||
|
@ -271,7 +276,7 @@ inspector_port = 8083
|
||||||
# static_files = [ "./functions/MY_FUNCTION_NAME/*.html" ]
|
# static_files = [ "./functions/MY_FUNCTION_NAME/*.html" ]
|
||||||
|
|
||||||
[analytics]
|
[analytics]
|
||||||
enabled = true
|
enabled = false
|
||||||
port = 54327
|
port = 54327
|
||||||
# Configure one of the supported backends: `postgres`, `bigquery`.
|
# Configure one of the supported backends: `postgres`, `bigquery`.
|
||||||
backend = "postgres"
|
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