diff --git a/api/package-lock.json b/api/package-lock.json index 825ae79..7325b38 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -11,10 +11,12 @@ "dotenv": "^16.5.0", "hono": "^4.7.7", "hono-sessions": "^0.7.2", + "nodemailer": "^7.0.4", "stream-chat": "^9.8.0" }, "devDependencies": { "@types/node": "^20.11.17", + "@types/nodemailer": "^6.4.17", "tsx": "^4.7.1", "typescript": "^5.8.3" } @@ -555,6 +557,15 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/nodemailer": { + "version": "6.4.17", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz", + "integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/phoenix": { "version": "1.6.6", "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", @@ -1093,6 +1104,14 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/nodemailer": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.4.tgz", + "integrity": "sha512-9O00Vh89/Ld2EcVCqJ/etd7u20UhME0f/NToPfArwPEe1Don1zy4mAIz6ariRr7mJ2RDxtaDzN0WJVdVXPtZaw==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", diff --git a/api/package.json b/api/package.json index 7f4aa64..1579f76 100644 --- a/api/package.json +++ b/api/package.json @@ -12,10 +12,12 @@ "dotenv": "^16.5.0", "hono": "^4.7.7", "hono-sessions": "^0.7.2", + "nodemailer": "^7.0.4", "stream-chat": "^9.8.0" }, "devDependencies": { "@types/node": "^20.11.17", + "@types/nodemailer": "^6.4.17", "tsx": "^4.7.1", "typescript": "^5.8.3" } diff --git a/api/src/database.types.ts b/api/src/database.types.ts new file mode 100644 index 0000000..28a1385 --- /dev/null +++ b/api/src/database.types.ts @@ -0,0 +1,309 @@ +export type Json = + | string + | number + | boolean + | null + | { [key: string]: Json | undefined } + | Json[] + +export type Database = { + public: { + Tables: { + devis: { + Row: { + client_email: string + created_at: string + date: string + due_date: string + id: string + items: Json + notes: string | null + number: string + status: Database["public"]["Enums"]["devis_status"] + subtotal: number + tax: number + terms: string | null + total: number + updated_at: string + user_id: string + } + Insert: { + client_email: string + created_at?: string + date: string + due_date: string + id?: string + items?: Json + notes?: string | null + number: string + status?: Database["public"]["Enums"]["devis_status"] + subtotal: number + tax: number + terms?: string | null + total: number + updated_at?: string + user_id: string + } + Update: { + client_email?: string + created_at?: string + date?: string + due_date?: string + id?: string + items?: Json + notes?: string | null + number?: string + status?: Database["public"]["Enums"]["devis_status"] + subtotal?: number + tax?: number + terms?: string | null + total?: number + updated_at?: string + user_id?: string + } + Relationships: [] + } + feedbacks: { + Row: { + created_at: string | null + fd_type: string + id: number + message: string + user_id: string + } + Insert: { + created_at?: string | null + fd_type: string + id?: number + message: string + user_id: string + } + Update: { + created_at?: string | null + fd_type?: string + id?: number + message?: string + user_id?: string + } + Relationships: [] + } + profiles: { + Row: { + avatar_url: string | null + email: string | null + id: string + name: string | null + } + Insert: { + avatar_url?: string | null + email?: string | null + id: string + name?: string | null + } + Update: { + avatar_url?: string | null + email?: string | null + id?: string + name?: string | null + } + Relationships: [] + } + tablo_invitations: { + Row: { + created_at: string | null + id: number + invited_by: string + invited_email: string + status: string + tablo_id: number + } + Insert: { + created_at?: string | null + id?: number + invited_by: string + invited_email: string + status?: string + tablo_id: number + } + Update: { + created_at?: string | null + id?: number + invited_by?: string + invited_email?: string + status?: string + tablo_id?: number + } + Relationships: [ + { + foreignKeyName: "fk_tablo_invitations_tablo_id" + columns: ["tablo_id"] + isOneToOne: false + referencedRelation: "tablos" + referencedColumns: ["id"] + }, + ] + } + tablos: { + Row: { + color: string | null + created_at: string | null + deleted_at: string | null + id: number + image: string | null + name: string + position: number + status: string + user_id: string + } + Insert: { + color?: string | null + created_at?: string | null + deleted_at?: string | null + id?: number + image?: string | null + name: string + position?: number + status?: string + user_id: string + } + Update: { + color?: string | null + created_at?: string | null + deleted_at?: string | null + id?: number + image?: string | null + name?: string + position?: number + status?: string + user_id?: string + } + Relationships: [] + } + } + Views: { + [_ in never]: never + } + Functions: { + [_ in never]: never + } + Enums: { + devis_status: "draft" | "sent" | "accepted" | "rejected" | "expired" + } + CompositeTypes: { + [_ in never]: never + } + } +} + +type DefaultSchema = Database[Extract] + +export type Tables< + DefaultSchemaTableNameOrOptions extends + | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) + | { schema: keyof Database }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof Database + } + ? keyof (Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + Database[DefaultSchemaTableNameOrOptions["schema"]]["Views"]) + : never = never, +> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } + ? (Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + Database[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends { + Row: infer R + } + ? R + : never + : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] & + DefaultSchema["Views"]) + ? (DefaultSchema["Tables"] & + DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { + Row: infer R + } + ? R + : never + : never + +export type TablesInsert< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof Database }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof Database + } + ? keyof Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, +> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } + ? Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Insert: infer I + } + ? I + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Insert: infer I + } + ? I + : never + : never + +export type TablesUpdate< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof Database }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof Database + } + ? keyof Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, +> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } + ? Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Update: infer U + } + ? U + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Update: infer U + } + ? U + : never + : never + +export type Enums< + DefaultSchemaEnumNameOrOptions extends + | keyof DefaultSchema["Enums"] + | { schema: keyof Database }, + EnumName extends DefaultSchemaEnumNameOrOptions extends { + schema: keyof Database + } + ? keyof Database[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"] + : never = never, +> = DefaultSchemaEnumNameOrOptions extends { schema: keyof Database } + ? Database[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName] + : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] + ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] + : never + +export type CompositeTypes< + PublicCompositeTypeNameOrOptions extends + | keyof DefaultSchema["CompositeTypes"] + | { schema: keyof Database }, + CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { + schema: keyof Database + } + ? keyof Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"] + : never = never, +> = PublicCompositeTypeNameOrOptions extends { schema: keyof Database } + ? Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName] + : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"] + ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] + : never + +export const Constants = { + public: { + Enums: { + devis_status: ["draft", "sent", "accepted", "rejected", "expired"], + }, + }, +} as const diff --git a/api/src/middleware.ts b/api/src/middleware.ts index 1eb0edc..31af78d 100644 --- a/api/src/middleware.ts +++ b/api/src/middleware.ts @@ -1,5 +1,6 @@ import { createClient, type User } from "@supabase/supabase-js"; import type { Context, Next } from "hono"; +import nodemailer from "nodemailer"; // Create authentication middleware export const authMiddleware = async (c: Context, next: Next) => { @@ -35,3 +36,17 @@ export const supabaseMiddleware = async (c: Context, next: Next) => { c.set("supabase", supabase); await next(); }; + +export const emailMiddleware = async (c: Context, next: Next) => { + const transporter = nodemailer.createTransport({ + host: "smtp.gmail.com", + port: 465, + secure: true, + auth: { + user: process.env.EMAIL_USER, + pass: process.env.EMAIL_KEY, + }, + }); + c.set("transporter", transporter); + await next(); +}; diff --git a/api/src/user.ts b/api/src/user.ts index e2f0214..3433ab3 100644 --- a/api/src/user.ts +++ b/api/src/user.ts @@ -1,15 +1,19 @@ import { Hono } from "hono"; -import { authMiddleware } from "./middleware.js"; -import type { User } from "@supabase/supabase-js"; +import { authMiddleware, emailMiddleware } from "./middleware.js"; +import type { SupabaseClient, User } from "@supabase/supabase-js"; import { StreamChat } from "stream-chat"; +import type { Transporter } from "nodemailer"; export const userRouter = new Hono<{ Variables: { user: User; + supabase: SupabaseClient; + transporter: Transporter; }; }>(); userRouter.use(authMiddleware); +userRouter.use(emailMiddleware); userRouter.get("/get-stream-token", async (c) => { const user = c.get("user"); @@ -29,3 +33,28 @@ userRouter.get("/get-stream-token", async (c) => { token, }); }); + +userRouter.post("/invite", async (c) => { + const sender = c.get("user"); + // const supabase = c.get("supabase"); + const transporter = c.get("transporter"); + const { email: recipientmail, tablo_id } = await c.req.json(); + + const info = await transporter.sendMail({ + from: `${sender.email} via XTablo `, + to: recipientmail, + subject: "You have been invited to a tablo", + html: `

You have been invited to a tablo with the following link: https://xtablo.com/tablo/${tablo_id}

`, + }); + + // const { data, error } = await supabase.auth.admin.inviteUserByEmail( + // recipientmail, + // { + // data: { + // tablo_id, + // }, + // } + // ); + + return c.json({ data: info }); +}); diff --git a/sql/09_create_tablo_invitations_table.sql b/sql/09_create_tablo_invitations_table.sql new file mode 100644 index 0000000..64e553a --- /dev/null +++ b/sql/09_create_tablo_invitations_table.sql @@ -0,0 +1,39 @@ +-- Create tablo_invitations table +CREATE TABLE IF NOT EXISTS tablo_invitations ( + id SERIAL PRIMARY KEY, + tablo_id INTEGER NOT NULL, + invited_email VARCHAR(255) NOT NULL, + invited_by UUID NOT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'pending', + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + + -- Foreign key constraint to tablos table + CONSTRAINT fk_tablo_invitations_tablo_id + FOREIGN KEY (tablo_id) REFERENCES tablos(id) ON DELETE CASCADE, + + -- Constraint to ensure status is one of the allowed values + CONSTRAINT tablo_invitations_status_check + CHECK (status IN ('pending', 'accepted', 'declined')), + + -- Unique constraint to prevent duplicate invitations + CONSTRAINT unique_tablo_invitation + UNIQUE (tablo_id, invited_email) +); + +-- Enable Row Level Security +ALTER TABLE tablo_invitations ENABLE ROW LEVEL SECURITY; + +-- Create policy to allow tablo owners to insert invitations +CREATE POLICY "Tablo owners can insert invitations" ON tablo_invitations + FOR INSERT WITH CHECK ( + auth.uid() = invited_by AND + EXISTS ( + SELECT 1 FROM tablos + WHERE tablos.id = tablo_invitations.tablo_id + AND tablos.user_id = auth.uid() + ) + ); + +-- Create index for better query performance +CREATE INDEX idx_tablo_invitations_tablo_id ON tablo_invitations(tablo_id); +CREATE INDEX idx_tablo_invitations_invited_email ON tablo_invitations(invited_email); \ No newline at end of file diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 8f89cd9..cc06dad 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -31,7 +31,9 @@ export const App = () => { -
+
}> void; onCreate: ( - tabloData: Omit + tabloData: Omit< + Tablo, + "id" | "user_id" | "created_at" | "deleted_at" | "position" + > ) => void; } diff --git a/ui/src/components/TabloModal.tsx b/ui/src/components/TabloModal.tsx index 19ab65d..337d148 100644 --- a/ui/src/components/TabloModal.tsx +++ b/ui/src/components/TabloModal.tsx @@ -3,6 +3,7 @@ import { useState } from "react"; import { ImageColorPicker } from "./ImageColorPicker"; import { StatusPicker } from "./StatusPicker"; import { Database } from "@ui/types/database.types"; +import { useInviteUser } from "@ui/hooks/invite"; type Tablo = Database["public"]["Tables"]["tablos"]["Row"]; type StatusType = "todo" | "in_progress" | "done"; @@ -22,6 +23,9 @@ export const TabloModal = ({ tablo, onClose, onEdit }: TabloModalProps) => { tablo?.color || "bg-blue-500" ); + const [inviteEmail, setInviteEmail] = useState(""); + const inviteUser = useInviteUser(); + const handleCancelEdit = () => { setEditData(null); }; @@ -38,6 +42,18 @@ export const TabloModal = ({ tablo, onClose, onEdit }: TabloModalProps) => { } }; + const handleSendInvite = () => { + if (inviteEmail.trim()) { + inviteUser({ email: inviteEmail, tablo_id: tablo?.id ?? 0 }); + setInviteEmail(""); + } + }; + + const isEmailValid = (email: string): boolean => { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); + }; + if (!tablo) return null; const currentData = editData || tablo; @@ -96,6 +112,30 @@ export const TabloModal = ({ tablo, onClose, onEdit }: TabloModalProps) => { } />
+ + {/* Invite User Section */} +
+

+ Inviter un utilisateur +

+
+ setInviteEmail(e.target.value)} + placeholder="Email de l'utilisateur à inviter" + className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white" + /> + +
+
diff --git a/ui/src/hooks/invite.ts b/ui/src/hooks/invite.ts new file mode 100644 index 0000000..2e52efe --- /dev/null +++ b/ui/src/hooks/invite.ts @@ -0,0 +1,32 @@ +import { useMutation } from "@tanstack/react-query"; +import { api } from "@ui/lib/api"; +import { useSession } from "@ui/contexts/SessionContext"; + +// Invite user by email +export const useInviteUser = () => { + const { session } = useSession(); + const { mutate } = useMutation({ + mutationFn: async ({ + email, + tablo_id, + }: { + email: string; + tablo_id: number; + }) => { + const { data } = await api.post( + "/api/v1/users/invite", + { + email, + tablo_id, + }, + { + headers: { + Authorization: `Bearer ${session?.access_token}`, + }, + } + ); + return data; + }, + }); + return mutate; +}; diff --git a/ui/src/pages/tablo.tsx b/ui/src/pages/tablo.tsx index 0a8fd82..5fce8ba 100644 --- a/ui/src/pages/tablo.tsx +++ b/ui/src/pages/tablo.tsx @@ -11,6 +11,7 @@ import { } from "@ui/hooks/tablos"; import { Database } from "@ui/types/database.types"; import { LoadingSpinner } from "@ui/components/LoadingSpinner"; +import { useSession } from "@ui/contexts/SessionContext"; type Tablo = Database["public"]["Tables"]["tablos"]["Row"]; @@ -21,6 +22,7 @@ export const TabloPage = () => { const [deletingTablo, setDeletingTablo] = useState(null); const [isDeleting, setIsDeleting] = useState(false); + const { session } = useSession(); const { data: tablos, isLoading, error } = useTablosList(); const createTabloMutation = useCreateTablo(); const { mutateAsync: updateTablo } = useUpdateTablo(); @@ -41,7 +43,10 @@ export const TabloPage = () => { }; const createNewTablo = async ( - tabloData: Omit + tabloData: Omit< + Tablo, + "id" | "user_id" | "created_at" | "deleted_at" | "position" + > ) => { try { await createTabloMutation.mutateAsync(tabloData); @@ -145,6 +150,18 @@ export const TabloPage = () => { setIsDeleting(false); }; + const getUserRole = (tablo: Tablo) => { + if (!session?.user) return "Invité"; + return tablo.user_id === session.user.id ? "Admin" : "Invité"; + }; + + const getRoleColor = (tablo: Tablo) => { + if (!session?.user) return "text-gray-500 dark:text-gray-400"; + return tablo.user_id === session.user.id + ? "text-blue-600 dark:text-blue-400" + : "text-gray-500 dark:text-gray-400"; + }; + // Show loading state if (isLoading) { return ( @@ -303,17 +320,34 @@ export const TabloPage = () => { {/* Content */}
-
-

- {tablo.name} -

- {/* Status badge */} +
+
+

+ {tablo.name} +

+ {/* Status badge */} +
+ {getStatusLabel(tablo.status)} +
+
- {getStatusLabel(tablo.status)} + + + + {getUserRole(tablo)}
@@ -458,7 +492,7 @@ export const TabloPage = () => {
{tablos && tablos.length > 0 ? ( -
+
{/* Render tablos */} {tablos.map((tablo) => renderTablo(tablo))}
diff --git a/ui/src/types/database.types.ts b/ui/src/types/database.types.ts index 4e83597..28a1385 100644 --- a/ui/src/types/database.types.ts +++ b/ui/src/types/database.types.ts @@ -4,263 +4,301 @@ export type Json = | boolean | null | { [key: string]: Json | undefined } - | Json[]; + | Json[] export type Database = { public: { Tables: { devis: { Row: { - client_email: string; - created_at: string; - date: string; - due_date: string; - id: string; - items: Json; - notes: string | null; - number: string; - status: Database["public"]["Enums"]["devis_status"]; - subtotal: number; - tax: number; - terms: string | null; - total: number; - updated_at: string; - user_id: string; - }; + client_email: string + created_at: string + date: string + due_date: string + id: string + items: Json + notes: string | null + number: string + status: Database["public"]["Enums"]["devis_status"] + subtotal: number + tax: number + terms: string | null + total: number + updated_at: string + user_id: string + } Insert: { - client_email: string; - created_at?: string; - date: string; - due_date: string; - id?: string; - items?: Json; - notes?: string | null; - number: string; - status?: Database["public"]["Enums"]["devis_status"]; - subtotal: number; - tax: number; - terms?: string | null; - total: number; - updated_at?: string; - user_id: string; - }; + client_email: string + created_at?: string + date: string + due_date: string + id?: string + items?: Json + notes?: string | null + number: string + status?: Database["public"]["Enums"]["devis_status"] + subtotal: number + tax: number + terms?: string | null + total: number + updated_at?: string + user_id: string + } Update: { - client_email?: string; - created_at?: string; - date?: string; - due_date?: string; - id?: string; - items?: Json; - notes?: string | null; - number?: string; - status?: Database["public"]["Enums"]["devis_status"]; - subtotal?: number; - tax?: number; - terms?: string | null; - total?: number; - updated_at?: string; - user_id?: string; - }; - Relationships: []; - }; + client_email?: string + created_at?: string + date?: string + due_date?: string + id?: string + items?: Json + notes?: string | null + number?: string + status?: Database["public"]["Enums"]["devis_status"] + subtotal?: number + tax?: number + terms?: string | null + total?: number + updated_at?: string + user_id?: string + } + Relationships: [] + } feedbacks: { Row: { - created_at: string | null; - fd_type: string; - id: number; - message: string; - user_id: string; - }; + created_at: string | null + fd_type: string + id: number + message: string + user_id: string + } Insert: { - created_at?: string | null; - fd_type: string; - id?: number; - message: string; - user_id: string; - }; + created_at?: string | null + fd_type: string + id?: number + message: string + user_id: string + } Update: { - created_at?: string | null; - fd_type?: string; - id?: number; - message?: string; - user_id?: string; - }; - Relationships: []; - }; + created_at?: string | null + fd_type?: string + id?: number + message?: string + user_id?: string + } + Relationships: [] + } profiles: { Row: { - avatar_url: string | null; - email: string | null; - id: string; - name: string | null; - }; + avatar_url: string | null + email: string | null + id: string + name: string | null + } Insert: { - avatar_url?: string | null; - email?: string | null; - id: string; - name?: string | null; - }; + avatar_url?: string | null + email?: string | null + id: string + name?: string | null + } Update: { - avatar_url?: string | null; - email?: string | null; - id?: string; - name?: string | null; - }; - Relationships: []; - }; + avatar_url?: string | null + email?: string | null + id?: string + name?: string | null + } + Relationships: [] + } + tablo_invitations: { + Row: { + created_at: string | null + id: number + invited_by: string + invited_email: string + status: string + tablo_id: number + } + Insert: { + created_at?: string | null + id?: number + invited_by: string + invited_email: string + status?: string + tablo_id: number + } + Update: { + created_at?: string | null + id?: number + invited_by?: string + invited_email?: string + status?: string + tablo_id?: number + } + Relationships: [ + { + foreignKeyName: "fk_tablo_invitations_tablo_id" + columns: ["tablo_id"] + isOneToOne: false + referencedRelation: "tablos" + referencedColumns: ["id"] + }, + ] + } tablos: { Row: { - color: string | null; - created_at: string | null; - deleted_at: string | null; - id: number; - image: string | null; - name: string; - status: string; - user_id: string; - }; + color: string | null + created_at: string | null + deleted_at: string | null + id: number + image: string | null + name: string + position: number + status: string + user_id: string + } Insert: { - color?: string | null; - created_at?: string | null; - deleted_at?: string | null; - id?: number; - image?: string | null; - name: string; - status?: string; - user_id: string; - }; + color?: string | null + created_at?: string | null + deleted_at?: string | null + id?: number + image?: string | null + name: string + position?: number + status?: string + user_id: string + } Update: { - color?: string | null; - created_at?: string | null; - deleted_at?: string | null; - id?: number; - image?: string | null; - name?: string; - status?: string; - user_id?: string; - }; - Relationships: []; - }; - }; + color?: string | null + created_at?: string | null + deleted_at?: string | null + id?: number + image?: string | null + name?: string + position?: number + status?: string + user_id?: string + } + Relationships: [] + } + } Views: { - [_ in never]: never; - }; + [_ in never]: never + } Functions: { - [_ in never]: never; - }; + [_ in never]: never + } Enums: { - devis_status: "draft" | "sent" | "accepted" | "rejected" | "expired"; - }; + devis_status: "draft" | "sent" | "accepted" | "rejected" | "expired" + } CompositeTypes: { - [_ in never]: never; - }; - }; -}; + [_ in never]: never + } + } +} -type DefaultSchema = Database[Extract]; +type DefaultSchema = Database[Extract] export type Tables< DefaultSchemaTableNameOrOptions extends | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) | { schema: keyof Database }, TableName extends DefaultSchemaTableNameOrOptions extends { - schema: keyof Database; + schema: keyof Database } ? keyof (Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & Database[DefaultSchemaTableNameOrOptions["schema"]]["Views"]) - : never = never + : never = never, > = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } ? (Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & Database[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends { - Row: infer R; + Row: infer R } ? R : never : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] & - DefaultSchema["Views"]) - ? (DefaultSchema["Tables"] & - DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { - Row: infer R; - } - ? R + DefaultSchema["Views"]) + ? (DefaultSchema["Tables"] & + DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { + Row: infer R + } + ? R + : never : never - : never; export type TablesInsert< DefaultSchemaTableNameOrOptions extends | keyof DefaultSchema["Tables"] | { schema: keyof Database }, TableName extends DefaultSchemaTableNameOrOptions extends { - schema: keyof Database; + schema: keyof Database } ? keyof Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] - : never = never + : never = never, > = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } ? Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { - Insert: infer I; + Insert: infer I } ? I : never : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] - ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { - Insert: infer I; - } - ? I + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Insert: infer I + } + ? I + : never : never - : never; export type TablesUpdate< DefaultSchemaTableNameOrOptions extends | keyof DefaultSchema["Tables"] | { schema: keyof Database }, TableName extends DefaultSchemaTableNameOrOptions extends { - schema: keyof Database; + schema: keyof Database } ? keyof Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] - : never = never + : never = never, > = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } ? Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { - Update: infer U; + Update: infer U } ? U : never : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] - ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { - Update: infer U; - } - ? U + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Update: infer U + } + ? U + : never : never - : never; export type Enums< DefaultSchemaEnumNameOrOptions extends | keyof DefaultSchema["Enums"] | { schema: keyof Database }, EnumName extends DefaultSchemaEnumNameOrOptions extends { - schema: keyof Database; + schema: keyof Database } ? keyof Database[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"] - : never = never + : never = never, > = DefaultSchemaEnumNameOrOptions extends { schema: keyof Database } ? Database[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName] : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] - ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] - : never; + ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] + : never export type CompositeTypes< PublicCompositeTypeNameOrOptions extends | keyof DefaultSchema["CompositeTypes"] | { schema: keyof Database }, CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { - schema: keyof Database; + schema: keyof Database } ? keyof Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"] - : never = never + : never = never, > = PublicCompositeTypeNameOrOptions extends { schema: keyof Database } ? Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName] : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"] - ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] - : never; + ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] + : never export const Constants = { public: { @@ -268,4 +306,4 @@ export const Constants = { devis_status: ["draft", "sent", "accepted", "rejected", "expired"], }, }, -} as const; +} as const