diff --git a/api/src/database.types.ts b/api/src/database.types.ts index a498a4c..665cc26 100644 --- a/api/src/database.types.ts +++ b/api/src/database.types.ts @@ -4,298 +4,298 @@ 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_invites: { Row: { - id: number - invite_token: string - invited_by: string - invited_email: string - tablo_id: number - } + id: number; + invite_token: string; + invited_by: string; + invited_email: string; + tablo_id: string; + }; Insert: { - id?: number - invite_token: string - invited_by: string - invited_email: string - tablo_id: number - } + id?: number; + invite_token: string; + invited_by: string; + invited_email: string; + tablo_id: string; + }; Update: { - id?: number - invite_token?: string - invited_by?: string - invited_email?: string - tablo_id?: number - } + id?: number; + invite_token?: string; + invited_by?: string; + invited_email?: string; + tablo_id?: string; + }; Relationships: [ { - foreignKeyName: "fk_tablo_invitations_tablo_id" - columns: ["tablo_id"] - isOneToOne: false - referencedRelation: "tablos" - referencedColumns: ["id"] - }, - ] - } + 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 - owner_id: string - position: number - status: string - } + color: string | null; + created_at: string | null; + deleted_at: string | null; + id: string; + image: string | null; + name: string; + owner_id: string; + position: number; + status: string; + }; Insert: { - color?: string | null - created_at?: string | null - deleted_at?: string | null - id?: number - image?: string | null - name: string - owner_id: string - position?: number - status?: string - } + color?: string | null; + created_at?: string | null; + deleted_at?: string | null; + id?: string; + image?: string | null; + name: string; + owner_id: string; + position?: number; + status?: string; + }; Update: { - color?: string | null - created_at?: string | null - deleted_at?: string | null - id?: number - image?: string | null - name?: string - owner_id?: string - position?: number - status?: string - } - Relationships: [] - } - } + color?: string | null; + created_at?: string | null; + deleted_at?: string | null; + id?: string; + image?: string | null; + name?: string; + owner_id?: string; + position?: number; + status?: 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 - : never + 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 + 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 - : never + ? 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 + 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 - : never + ? 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 + 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: { @@ -303,4 +303,4 @@ export const Constants = { devis_status: ["draft", "sent", "accepted", "rejected", "expired"], }, }, -} as const +} as const; diff --git a/api/src/middleware.ts b/api/src/middleware.ts index d36858b..3f9f1c7 100644 --- a/api/src/middleware.ts +++ b/api/src/middleware.ts @@ -1,6 +1,7 @@ import { createClient, type User } from "@supabase/supabase-js"; import type { Context, Next } from "hono"; import nodemailer from "nodemailer"; +import { StreamChat } from "stream-chat"; // Create authentication middleware export const authMiddleware = async (c: Context, next: Next) => { @@ -50,3 +51,15 @@ export const emailMiddleware = async (c: Context, next: Next) => { c.set("transporter", transporter); await next(); }; + +export const streamChatMiddleware = async (c: Context, next: Next) => { + const serverClient = StreamChat.getInstance( + process.env.STREAM_CHAT_API_KEY as string, + process.env.STREAM_CHAT_API_SECRET as string, + { + disableCache: true, + } + ); + c.set("streamServerClient", serverClient); + await next(); +}; diff --git a/api/src/routers.ts b/api/src/routers.ts index 5a0466b..80eab74 100644 --- a/api/src/routers.ts +++ b/api/src/routers.ts @@ -1,6 +1,7 @@ import { Hono } from "hono"; import { userRouter } from "./user.js"; import { supabaseMiddleware } from "./middleware.js"; +import { tabloRouter } from "./tablo.js"; export const mainRouter = new Hono<{ Bindings: { @@ -28,3 +29,4 @@ mainRouter.use(supabaseMiddleware); // ); mainRouter.route("/users", userRouter); +mainRouter.route("/tablos", tabloRouter); diff --git a/api/src/tablo.ts b/api/src/tablo.ts new file mode 100644 index 0000000..bf4d705 --- /dev/null +++ b/api/src/tablo.ts @@ -0,0 +1,154 @@ +import { Hono } from "hono"; +import { + authMiddleware, + emailMiddleware, + streamChatMiddleware, +} from "./middleware.js"; +import type { SupabaseClient, User } from "@supabase/supabase-js"; +import type { Transporter } from "nodemailer"; +import { generateToken } from "./token.js"; +import { config } from "./config.js"; +import type { Database, Tables } from "./database.types.js"; +import type { StreamChat } from "stream-chat"; + +type Tablo = Database["public"]["Tables"]["tablos"]; +type TabloInsert = Tablo["Insert"]; + +export const tabloRouter = new Hono<{ + Variables: { + user: User; + supabase: SupabaseClient; + transporter: Transporter; + streamServerClient: StreamChat; + }; +}>(); + +tabloRouter.use(authMiddleware); +tabloRouter.use(emailMiddleware); +tabloRouter.use(streamChatMiddleware); + +tabloRouter.post("/create", async (c) => { + const user = c.get("user"); + const supabase = c.get("supabase"); + const data = await c.req.json(); + + const tablo = data as Omit; + + const { error } = await supabase.from("tablos").insert({ + ...tablo, + owner_id: user.id, + }); + + if (error) { + return c.json({ error: error.message }, 500); + } + + const streamServerClient = c.get("streamServerClient"); + const channel = streamServerClient.channel("messaging", tablo.name, { + // @ts-ignore + name: tablo.name, + created_by_id: user.id, + members: [user.id], + }); + await channel.create(); + await channel.addMembers([user.id]); + + return c.json({ message: "Tablo created successfully" }); +}); + +tabloRouter.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 token = generateToken(); + + const { data, error: tabloError } = await supabase + .from("tablos") + .select("*") + .eq("id", tablo_id) + .single(); + + const tablo = data as Tables<"tablos">; + + if (tabloError) { + return c.json({ error: tabloError.message }, 500); + } + + if (!tablo) { + return c.json({ error: "Tablo not found" }, 404); + } + + if (tablo.owner_id !== sender.id) { + return c.json( + { error: "You are not allowed to invite users to this tablo" }, + 400 + ); + } + + const { error } = await supabase.from("tablo_invites").insert({ + invited_email: recipientmail, + tablo_id: tablo_id, + invited_by: sender.id, + invite_token: token, + }); + + if (error) { + return c.json({ error: error.message }, 500); + } + + const info = await transporter.sendMail({ + from: `${sender.email} via XTablo `, + to: recipientmail, + subject: "Vous avez été invité à un tablo", + html: `

Vous avez été invité à un tablo avec ce lien

`, + }); + + return c.json({ + message: "Invite sent successfully", + }); +}); + +tabloRouter.post("/join-tablo", async (c) => { + const { token } = await c.req.json(); + + const joiner = c.get("user"); + const supabase = c.get("supabase"); + + const { data, error } = await supabase + .from("tablo_invites") + .select("id, tablo_id, invited_by") + .eq("invite_token", token) + .eq("invited_email", joiner.email) + .single(); + + if (error) { + return c.json({ error: error.message }, 500); + } + + if (!data) { + return c.json({ error: "Invalid token or email" }, 400); + } + + const { id: invite_id, tablo_id, invited_by } = data; + + const { error: tabloAccessError } = await supabase + .from("tablo_access") + .insert({ + tablo_id, + user_id: joiner.id, + // ** IMPORTANT ** + is_admin: false, + // ------------- + is_active: true, + granted_by: invited_by, + }); + + if (tabloAccessError) { + return c.json({ error: tabloAccessError.message }, 500); + } + + await supabase.from("tablo_invites").delete().eq("id", invite_id); + return c.json({ message: "Tablo joined successfully" }); +}); diff --git a/api/src/user.ts b/api/src/user.ts index 0a198e6..517bf52 100644 --- a/api/src/user.ts +++ b/api/src/user.ts @@ -1,10 +1,8 @@ import { Hono } from "hono"; -import { authMiddleware, emailMiddleware } from "./middleware.js"; +import { authMiddleware, streamChatMiddleware } from "./middleware.js"; import type { SupabaseClient, User } from "@supabase/supabase-js"; import { StreamChat } from "stream-chat"; import type { Transporter } from "nodemailer"; -import { generateToken } from "./token.js"; -import { config } from "./config.js"; import type { Tables } from "./database.types.js"; export const userRouter = new Hono<{ @@ -12,124 +10,65 @@ export const userRouter = new Hono<{ user: User; supabase: SupabaseClient; transporter: Transporter; + streamServerClient: StreamChat; }; }>(); userRouter.use(authMiddleware); -userRouter.use(emailMiddleware); +userRouter.use(streamChatMiddleware); -userRouter.get("/get-stream-token", async (c) => { - const user = c.get("user"); - - const user_id = user.id; - const serverClient = new StreamChat( - process.env.STREAM_CHAT_API_KEY as string, - process.env.STREAM_CHAT_API_SECRET as string, - { - disableCache: true, - } - ); - - const token = serverClient.createToken(user_id); - - return c.json({ - token, - }); -}); - -userRouter.post("/invite", async (c) => { - const sender = c.get("user"); +userRouter.post("/sign-up-to-stream", async (c) => { + const { id } = c.get("user"); const supabase = c.get("supabase"); - const transporter = c.get("transporter"); - const { email: recipientmail, tablo_id } = await c.req.json(); - const token = generateToken(); - - const { data, error: tabloError } = await supabase - .from("tablos") + const { data } = await supabase + .from("profiles") .select("*") - .eq("id", tablo_id) + .eq("id", id) .single(); - const tablo = data as Tables<"tablos">; + const user = data as Tables<"profiles">; - if (tabloError) { - return c.json({ error: tabloError.message }, 500); - } - - if (!tablo) { - return c.json({ error: "Tablo not found" }, 404); - } - - if (tablo.owner_id !== sender.id) { - return c.json( - { error: "You are not allowed to invite users to this tablo" }, - 400 - ); - } - - const { error } = await supabase.from("tablo_invites").insert({ - invited_email: recipientmail, - tablo_id: tablo_id, - invited_by: sender.id, - invite_token: token, - }); - - if (error) { - return c.json({ error: error.message }, 500); - } - - const info = await transporter.sendMail({ - from: `${sender.email} via XTablo `, - to: recipientmail, - subject: "Vous avez été invité à un tablo", - html: `

Vous avez été invité à un tablo avec ce lien

`, + const streamServerClient = c.get("streamServerClient"); + await streamServerClient.upsertUser({ + id, + name: user.name ?? "", + language: "fr", }); return c.json({ - message: "Invite sent successfully", + message: "User signed up to stream", }); }); -userRouter.post("/join-tablo", async (c) => { - const { token } = await c.req.json(); - - const joiner = c.get("user"); +userRouter.get("/me", async (c) => { + const user = c.get("user"); const supabase = c.get("supabase"); + const streamServerClient = c.get("streamServerClient"); const { data, error } = await supabase - .from("tablo_invites") - .select("id, tablo_id, invited_by") - .eq("invite_token", token) - .eq("invited_email", joiner.email) + .from("profiles") + .select("*") + .eq("id", user.id) .single(); + const userData = data as Tables<"profiles">; + + if (!userData) { + return c.json({ error: "User not found" }, 404); + } + if (error) { return c.json({ error: error.message }, 500); } - if (!data) { - return c.json({ error: "Invalid token or email" }, 400); - } + const user_id = data.id; + const token = streamServerClient.createToken(user_id); - const { id: invite_id, tablo_id, invited_by } = data; - - const { error: tabloAccessError } = await supabase - .from("tablo_access") - .insert({ - tablo_id, - user_id: joiner.id, - // ** IMPORTANT ** - is_admin: false, - // ------------- - is_active: true, - granted_by: invited_by, - }); - - if (tabloAccessError) { - return c.json({ error: tabloAccessError.message }, 500); - } - - await supabase.from("tablo_invites").delete().eq("id", invite_id); - return c.json({ message: "Tablo joined successfully" }); + return c.json({ + ...userData, + streamToken: token, + }); }); + +userRouter.post(""); diff --git a/sql/06_sample_data_and_queries.sql b/sql/06_sample_data_and_queries.sql index 73c327a..7f47719 100644 --- a/sql/06_sample_data_and_queries.sql +++ b/sql/06_sample_data_and_queries.sql @@ -4,41 +4,41 @@ -- Sample tablos data INSERT INTO tablos (id, name, description, color, owner_id, is_public) VALUES -('550e8400-e29b-41d4-a716-446655440001', 'Projet Alpha', 'Développement de la nouvelle application mobile', 'bg-blue-500', auth.uid(), false), -('550e8400-e29b-41d4-a716-446655440002', 'Marketing Q4', 'Campagnes marketing pour le quatrième trimestre 2024', 'bg-green-500', auth.uid(), true), -('550e8400-e29b-41d4-a716-446655440003', 'Équipe Dev', 'Coordination et suivi de l''équipe de développement', 'bg-purple-500', auth.uid(), false), -('550e8400-e29b-41d4-a716-446655440004', 'Budget 2024', 'Planification et suivi budgétaire pour l''année 2024', 'bg-red-500', auth.uid(), false), -('550e8400-e29b-41d4-a716-446655440005', 'Roadmap Produit', 'Feuille de route et évolution du produit', 'bg-yellow-500', auth.uid(), true), -('550e8400-e29b-41d4-a716-446655440006', 'Support Client', 'Gestion et suivi du support client', 'bg-indigo-500', auth.uid(), false); +('A1B2C3D4E5F6G7H8I9J0K1L2', 'Projet Alpha', 'Développement de la nouvelle application mobile', 'bg-blue-500', auth.uid(), false), +('M3N4O5P6Q7R8S9T0U1V2W3X4', 'Marketing Q4', 'Campagnes marketing pour le quatrième trimestre 2024', 'bg-green-500', auth.uid(), true), +('Y5Z6A7B8C9D0E1F2G3H4I5J6', 'Équipe Dev', 'Coordination et suivi de l''équipe de développement', 'bg-purple-500', auth.uid(), false), +('K7L8M9N0O1P2Q3R4S5T6U7V8', 'Budget 2024', 'Planification et suivi budgétaire pour l''année 2024', 'bg-red-500', auth.uid(), false), +('W9X0Y1Z2A3B4C5D6E7F8G9H0', 'Roadmap Produit', 'Feuille de route et évolution du produit', 'bg-yellow-500', auth.uid(), true), +('I1J2K3L4M5N6O7P8Q9R0S1T2', 'Support Client', 'Gestion et suivi du support client', 'bg-indigo-500', auth.uid(), false); -- Sample boards for each tablo INSERT INTO tablo_boards (tablo_id, name, type, description, position, created_by) VALUES -- Projet Alpha boards -('550e8400-e29b-41d4-a716-446655440001', 'Développement', 'kanban', 'Suivi des tâches de développement', 0, auth.uid()), -('550e8400-e29b-41d4-a716-446655440001', 'Planning', 'calendar', 'Calendrier du projet', 1, auth.uid()), -('550e8400-e29b-41d4-a716-446655440001', 'Discussion', 'chat', 'Chat de l''équipe projet', 2, auth.uid()), +('A1B2C3D4E5F6G7H8I9J0K1L2', 'Développement', 'kanban', 'Suivi des tâches de développement', 0, auth.uid()), +('A1B2C3D4E5F6G7H8I9J0K1L2', 'Planning', 'calendar', 'Calendrier du projet', 1, auth.uid()), +('A1B2C3D4E5F6G7H8I9J0K1L2', 'Discussion', 'chat', 'Chat de l''équipe projet', 2, auth.uid()), -- Marketing Q4 boards -('550e8400-e29b-41d4-a716-446655440002', 'Campagnes', 'kanban', 'Suivi des campagnes marketing', 0, auth.uid()), -('550e8400-e29b-41d4-a716-446655440002', 'Calendrier Editorial', 'calendar', 'Planning des publications', 1, auth.uid()), +('M3N4O5P6Q7R8S9T0U1V2W3X4', 'Campagnes', 'kanban', 'Suivi des campagnes marketing', 0, auth.uid()), +('M3N4O5P6Q7R8S9T0U1V2W3X4', 'Calendrier Editorial', 'calendar', 'Planning des publications', 1, auth.uid()), -- Équipe Dev boards -('550e8400-e29b-41d4-a716-446655440003', 'Sprint Board', 'kanban', 'Tableau de bord du sprint actuel', 0, auth.uid()), -('550e8400-e29b-41d4-a716-446655440003', 'Backlog', 'table', 'Backlog produit', 1, auth.uid()); +('Y5Z6A7B8C9D0E1F2G3H4I5J6', 'Sprint Board', 'kanban', 'Tableau de bord du sprint actuel', 0, auth.uid()), +('Y5Z6A7B8C9D0E1F2G3H4I5J6', 'Backlog', 'table', 'Backlog produit', 1, auth.uid()); -- Sample lists for Kanban boards INSERT INTO tablo_lists (board_id, name, position, color) VALUES -- For Projet Alpha - Développement board -((SELECT id FROM tablo_boards WHERE name = 'Développement' AND tablo_id = '550e8400-e29b-41d4-a716-446655440001'), 'À faire', 0, 'bg-gray-200'), -((SELECT id FROM tablo_boards WHERE name = 'Développement' AND tablo_id = '550e8400-e29b-41d4-a716-446655440001'), 'En cours', 1, 'bg-blue-200'), -((SELECT id FROM tablo_boards WHERE name = 'Développement' AND tablo_id = '550e8400-e29b-41d4-a716-446655440001'), 'En test', 2, 'bg-yellow-200'), -((SELECT id FROM tablo_boards WHERE name = 'Développement' AND tablo_id = '550e8400-e29b-41d4-a716-446655440001'), 'Terminé', 3, 'bg-green-200'), +((SELECT id FROM tablo_boards WHERE name = 'Développement' AND tablo_id = 'A1B2C3D4E5F6G7H8I9J0K1L2'), 'À faire', 0, 'bg-gray-200'), +((SELECT id FROM tablo_boards WHERE name = 'Développement' AND tablo_id = 'A1B2C3D4E5F6G7H8I9J0K1L2'), 'En cours', 1, 'bg-blue-200'), +((SELECT id FROM tablo_boards WHERE name = 'Développement' AND tablo_id = 'A1B2C3D4E5F6G7H8I9J0K1L2'), 'En test', 2, 'bg-yellow-200'), +((SELECT id FROM tablo_boards WHERE name = 'Développement' AND tablo_id = 'A1B2C3D4E5F6G7H8I9J0K1L2'), 'Terminé', 3, 'bg-green-200'), -- For Marketing Q4 - Campagnes board -((SELECT id FROM tablo_boards WHERE name = 'Campagnes' AND tablo_id = '550e8400-e29b-41d4-a716-446655440002'), 'Idées', 0, 'bg-purple-200'), -((SELECT id FROM tablo_boards WHERE name = 'Campagnes' AND tablo_id = '550e8400-e29b-41d4-a716-446655440002'), 'En préparation', 1, 'bg-orange-200'), -((SELECT id FROM tablo_boards WHERE name = 'Campagnes' AND tablo_id = '550e8400-e29b-41d4-a716-446655440002'), 'En cours', 2, 'bg-blue-200'), -((SELECT id FROM tablo_boards WHERE name = 'Campagnes' AND tablo_id = '550e8400-e29b-41d4-a716-446655440002'), 'Terminées', 3, 'bg-green-200'); +((SELECT id FROM tablo_boards WHERE name = 'Campagnes' AND tablo_id = 'M3N4O5P6Q7R8S9T0U1V2W3X4'), 'Idées', 0, 'bg-purple-200'), +((SELECT id FROM tablo_boards WHERE name = 'Campagnes' AND tablo_id = 'M3N4O5P6Q7R8S9T0U1V2W3X4'), 'En préparation', 1, 'bg-orange-200'), +((SELECT id FROM tablo_boards WHERE name = 'Campagnes' AND tablo_id = 'M3N4O5P6Q7R8S9T0U1V2W3X4'), 'En cours', 2, 'bg-blue-200'), +((SELECT id FROM tablo_boards WHERE name = 'Campagnes' AND tablo_id = 'M3N4O5P6Q7R8S9T0U1V2W3X4'), 'Terminées', 3, 'bg-green-200'); -- Sample cards INSERT INTO tablo_cards (list_id, title, description, position, priority, due_date, created_by) VALUES @@ -53,10 +53,10 @@ INSERT INTO tablo_cards (list_id, title, description, position, priority, due_da -- Sample chat channels INSERT INTO tablo_chat_channels (tablo_id, name, type, description, created_by) VALUES -('550e8400-e29b-41d4-a716-446655440001', 'général', 'public', 'Discussion générale du projet Alpha', auth.uid()), -('550e8400-e29b-41d4-a716-446655440001', 'dev-team', 'private', 'Canal privé pour l''équipe de développement', auth.uid()), -('550e8400-e29b-41d4-a716-446655440002', 'marketing-general', 'public', 'Discussion générale marketing', auth.uid()), -('550e8400-e29b-41d4-a716-446655440003', 'daily-standup', 'public', 'Daily standup de l''équipe dev', auth.uid()); +('A1B2C3D4E5F6G7H8I9J0K1L2', 'général', 'public', 'Discussion générale du projet Alpha', auth.uid()), +('A1B2C3D4E5F6G7H8I9J0K1L2', 'dev-team', 'private', 'Canal privé pour l''équipe de développement', auth.uid()), +('M3N4O5P6Q7R8S9T0U1V2W3X4', 'marketing-general', 'public', 'Discussion générale marketing', auth.uid()), +('Y5Z6A7B8C9D0E1F2G3H4I5J6', 'daily-standup', 'public', 'Daily standup de l''équipe dev', auth.uid()); -- Sample chat messages INSERT INTO tablo_chat_messages (channel_id, user_id, content, message_type) VALUES diff --git a/sql/12_update_tablos_id_to_random_string.sql b/sql/12_update_tablos_id_to_random_string.sql new file mode 100644 index 0000000..eebf62b --- /dev/null +++ b/sql/12_update_tablos_id_to_random_string.sql @@ -0,0 +1,115 @@ +-- Migration: Update tablos table ID from SERIAL to random 24-character string +-- This migration changes the tablos.id column from SERIAL to TEXT with random 24-character string IDs + +-- Step 1: Create function to generate random 24-character strings +CREATE OR REPLACE FUNCTION generate_random_string(length INTEGER DEFAULT 24) +RETURNS TEXT +LANGUAGE plpgsql +AS $$ +DECLARE + chars TEXT := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + result TEXT := ''; + i INTEGER := 0; +BEGIN + FOR i IN 1..length LOOP + result := result || substr(chars, floor(random() * length(chars) + 1)::INTEGER, 1); + END LOOP; + RETURN result; +END; +$$; + +-- Step 2: Drop existing foreign key constraints +ALTER TABLE tablo_invites DROP CONSTRAINT IF EXISTS fk_tablo_invitations_tablo_id; +ALTER TABLE tablo_access DROP CONSTRAINT IF EXISTS fk_tablo_access_tablo_id; + +-- Step 3: Drop the trigger that creates tablo_access records +DROP TRIGGER IF EXISTS trigger_create_tablo_access ON tablos; + +-- Step 4: Create temporary columns for the new ID structure +ALTER TABLE tablos ADD COLUMN new_id TEXT; +ALTER TABLE tablo_invites ADD COLUMN new_tablo_id TEXT; +ALTER TABLE tablo_access ADD COLUMN new_tablo_id TEXT; + +-- Step 5: Generate new random IDs for existing tablos +UPDATE tablos SET new_id = generate_random_string(24); + +-- Step 6: Update foreign key references +UPDATE tablo_invites SET new_tablo_id = ( + SELECT new_id FROM tablos WHERE tablos.id = tablo_invites.tablo_id +); + +UPDATE tablo_access SET new_tablo_id = ( + SELECT new_id FROM tablos WHERE tablos.id = tablo_access.tablo_id +); + +-- Step 7: Drop old columns +ALTER TABLE tablos DROP COLUMN id; +ALTER TABLE tablo_invites DROP COLUMN tablo_id; +ALTER TABLE tablo_access DROP COLUMN tablo_id; + +-- Step 8: Rename new columns to original names +ALTER TABLE tablos RENAME COLUMN new_id TO id; +ALTER TABLE tablo_invites RENAME COLUMN new_tablo_id TO tablo_id; +ALTER TABLE tablo_access RENAME COLUMN new_tablo_id TO tablo_id; + +-- Step 9: Add constraints and indexes +ALTER TABLE tablos ADD PRIMARY KEY (id); +ALTER TABLE tablos ALTER COLUMN id SET NOT NULL; +ALTER TABLE tablos ALTER COLUMN id SET DEFAULT generate_random_string(24); + +-- Step 10: Re-add foreign key constraints +ALTER TABLE tablo_invites +ADD CONSTRAINT fk_tablo_invitations_tablo_id +FOREIGN KEY (tablo_id) REFERENCES tablos(id) ON DELETE CASCADE; + +ALTER TABLE tablo_access +ADD CONSTRAINT fk_tablo_access_tablo_id +FOREIGN KEY (tablo_id) REFERENCES tablos(id) ON DELETE CASCADE; + +-- Step 11: Ensure NOT NULL constraints on foreign keys +ALTER TABLE tablo_invites ALTER COLUMN tablo_id SET NOT NULL; +ALTER TABLE tablo_access ALTER COLUMN tablo_id SET NOT NULL; + +-- Step 12: Recreate the trigger function with updated signature +CREATE OR REPLACE FUNCTION create_tablo_access_for_owner() +RETURNS TRIGGER +SECURITY DEFINER +AS $$ +BEGIN + -- Insert a tablo_access record for the tablo owner + INSERT INTO tablo_access ( + tablo_id, + user_id, + granted_by, + is_active, + is_admin + ) VALUES ( + NEW.id, -- tablo_id: the newly created tablo's id (now TEXT) + NEW.owner_id, -- user_id: the tablo owner gets access + NEW.owner_id, -- granted_by: self-granted by the owner + TRUE, -- is_active: access is active + TRUE -- is_admin: owner has admin privileges + ); + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Step 13: Recreate the trigger +CREATE TRIGGER trigger_create_tablo_access + AFTER INSERT ON tablos + FOR EACH ROW + EXECUTE FUNCTION create_tablo_access_for_owner(); + +-- Step 14: Add comment to document the changes +COMMENT ON FUNCTION generate_random_string(INTEGER) IS + 'Generates a random alphanumeric string of specified length (default 24 characters)'; + +COMMENT ON COLUMN tablos.id IS + 'Primary key: random 24-character alphanumeric string'; + +COMMENT ON COLUMN tablo_invites.tablo_id IS + 'Foreign key reference to tablos.id (24-character string)'; + +COMMENT ON COLUMN tablo_access.tablo_id IS + 'Foreign key reference to tablos.id (24-character string)'; \ No newline at end of file diff --git a/ui/src/hooks/auth.ts b/ui/src/hooks/auth.ts index 172d658..579c22f 100644 --- a/ui/src/hooks/auth.ts +++ b/ui/src/hooks/auth.ts @@ -8,7 +8,7 @@ import { Session, createClient, } from "@supabase/supabase-js"; -import { queryClient } from "@ui/lib/api"; +import { api, queryClient } from "@ui/lib/api"; export type User = SupabaseUser & { user_metadata: { @@ -51,6 +51,7 @@ interface AuthResponse { export function useSignUp() { const navigate = useNavigate(); const [errors, setErrors] = useState>({}); + const { signUpToStream } = useSignUpToStream(); const { mutate, isPending } = useMutation< AuthResponse, { message: string; code: string }, @@ -69,6 +70,9 @@ export function useSignUp() { }, }); if (error) throw error; + if (response.session?.access_token) { + await signUpToStream(response.session.access_token); + } return response; }, onSuccess: () => { @@ -96,9 +100,27 @@ export function useSignUp() { return { mutate, isPending, errors }; } +export function useSignUpToStream() { + const { mutate: signUpToStream } = useMutation({ + mutationFn: async (accessToken: string) => { + const { data } = await api.post<{ streamToken: string }>( + "/api/v1/users/sign-up-to-stream", + {}, + { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + } + ); + return data; + }, + }); + return { signUpToStream }; +} export function useLoginEmail() { const navigate = useNavigate(); const [errors, setErrors] = useState>({}); + const { signUpToStream } = useSignUpToStream(); const { mutate, isPending } = useMutation< AuthResponse, { message: string; code: string }, @@ -110,6 +132,9 @@ export function useLoginEmail() { password: data.password.trim(), }); if (error) throw error; + if (response.session?.access_token) { + await signUpToStream(response.session.access_token); + } return response; }, onSuccess: () => { diff --git a/ui/src/hooks/tablos.ts b/ui/src/hooks/tablos.ts index 0e66865..5f9985d 100644 --- a/ui/src/hooks/tablos.ts +++ b/ui/src/hooks/tablos.ts @@ -2,6 +2,8 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Database } from "@ui/types/database.types"; import { supabase } from "./auth"; import { useSession } from "@ui/contexts/SessionContext"; +import { api } from "@ui/lib/api"; +import { toast } from "@ui/ui-library/toast/toast-queue"; type Tablo = Database["public"]["Tables"]["tablos"]; @@ -25,7 +27,7 @@ export const useTablosList = () => { }; // Fetch single tablo -export const useTablo = (id: number) => { +export const useTablo = (id: string) => { return useQuery({ queryKey: ["tablos", id], queryFn: async () => { @@ -47,15 +49,24 @@ export const useCreateTablo = () => { return useMutation({ mutationFn: async (tablo: Omit) => { - const { error } = await supabase.from("tablos").insert({ - ...tablo, - owner_id: session?.user.id ?? "", + const { data } = await api.post("/api/v1/tablos/create", tablo, { + headers: { + Authorization: `Bearer ${session?.access_token}`, + }, }); - if (error) throw error; + return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["tablos"] }); }, + onError: (error) => { + console.error(error); + toast.add({ + title: "Échec de la création du tablo", + description: "Veuillez réessayer", + type: "error", + }); + }, }); }; diff --git a/ui/src/pages/oauth-signin.tsx b/ui/src/pages/oauth-signin.tsx index d2a730a..b37fd56 100644 --- a/ui/src/pages/oauth-signin.tsx +++ b/ui/src/pages/oauth-signin.tsx @@ -1,17 +1,20 @@ import { useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { useSession } from "@ui/contexts/SessionContext"; +import { useSignUpToStream } from "@ui/hooks/auth"; export const OAuthSigninPage = () => { const navigate = useNavigate(); const { session } = useSession(); + const { signUpToStream } = useSignUpToStream(); useEffect(() => { const interval = setInterval(() => { if (session) { + signUpToStream(session.access_token); navigate("/"); } }, 100); return () => clearInterval(interval); - }, [navigate, session]); + }, [navigate, session, signUpToStream]); return <>; }; diff --git a/ui/src/providers/UserStoreProvider.tsx b/ui/src/providers/UserStoreProvider.tsx index 04a6c94..3c39055 100644 --- a/ui/src/providers/UserStoreProvider.tsx +++ b/ui/src/providers/UserStoreProvider.tsx @@ -1,6 +1,5 @@ import { createStore, StoreApi, useStore } from "zustand"; import React from "react"; -import { supabase } from "@ui/hooks/auth"; import { useQuery } from "@tanstack/react-query"; import { Tables } from "@ui/types/database.types"; import { useSession } from "@ui/contexts/SessionContext"; @@ -20,28 +19,20 @@ export const UserStoreProvider = ({ }) => { const { session } = useSession(); const shouldFetchUser = !!session?.access_token; - const { data, isPending } = useQuery({ + const { data: user, isPending } = useQuery({ queryKey: ["user"], queryFn: async () => { - const { data, error } = await supabase.from("profiles").select("*"); - if (error) throw error; - let token = null; try { - const { - data: { token: streamToken }, - } = await api.get("/api/v1/users/get-stream-token", { + const { data: user } = await api.get("/api/v1/users/me", { headers: { Authorization: `Bearer ${session?.access_token}`, }, }); - token = streamToken; + return user; } catch (error) { - console.error("Failed to get stream token:", error); + console.error("Failed to get user:", error); + return null; } - return { - ...data[0], - streamToken: token, - }; }, enabled: shouldFetchUser, }); @@ -50,11 +41,11 @@ export const UserStoreProvider = ({ return ; } - if (!data) { + if (!user) { return children; } - const store = createStore()(() => data); + const store = createStore()(() => user); return ( }> diff --git a/ui/src/types/database.types.ts b/ui/src/types/database.types.ts index a498a4c..665cc26 100644 --- a/ui/src/types/database.types.ts +++ b/ui/src/types/database.types.ts @@ -4,298 +4,298 @@ 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_invites: { Row: { - id: number - invite_token: string - invited_by: string - invited_email: string - tablo_id: number - } + id: number; + invite_token: string; + invited_by: string; + invited_email: string; + tablo_id: string; + }; Insert: { - id?: number - invite_token: string - invited_by: string - invited_email: string - tablo_id: number - } + id?: number; + invite_token: string; + invited_by: string; + invited_email: string; + tablo_id: string; + }; Update: { - id?: number - invite_token?: string - invited_by?: string - invited_email?: string - tablo_id?: number - } + id?: number; + invite_token?: string; + invited_by?: string; + invited_email?: string; + tablo_id?: string; + }; Relationships: [ { - foreignKeyName: "fk_tablo_invitations_tablo_id" - columns: ["tablo_id"] - isOneToOne: false - referencedRelation: "tablos" - referencedColumns: ["id"] - }, - ] - } + 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 - owner_id: string - position: number - status: string - } + color: string | null; + created_at: string | null; + deleted_at: string | null; + id: string; + image: string | null; + name: string; + owner_id: string; + position: number; + status: string; + }; Insert: { - color?: string | null - created_at?: string | null - deleted_at?: string | null - id?: number - image?: string | null - name: string - owner_id: string - position?: number - status?: string - } + color?: string | null; + created_at?: string | null; + deleted_at?: string | null; + id?: string; + image?: string | null; + name: string; + owner_id: string; + position?: number; + status?: string; + }; Update: { - color?: string | null - created_at?: string | null - deleted_at?: string | null - id?: number - image?: string | null - name?: string - owner_id?: string - position?: number - status?: string - } - Relationships: [] - } - } + color?: string | null; + created_at?: string | null; + deleted_at?: string | null; + id?: string; + image?: string | null; + name?: string; + owner_id?: string; + position?: number; + status?: 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 - : never + 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 + 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 - : never + ? 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 + 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 - : never + ? 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 + 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: { @@ -303,4 +303,4 @@ export const Constants = { devis_status: ["draft", "sent", "accepted", "rejected", "expired"], }, }, -} as const +} as const;