Few improvements regarding tablos and channels sync

This commit is contained in:
Arthur Belleville 2025-07-05 15:16:42 +02:00
parent 2bb2cb1f38
commit 991302eaaf
No known key found for this signature in database
12 changed files with 783 additions and 530 deletions

View file

@ -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<keyof Database, "public">]
type DefaultSchema = Database[Extract<keyof Database, "public">];
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;

View file

@ -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();
};

View file

@ -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);

154
api/src/tablo.ts Normal file
View file

@ -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<TabloInsert, "owner_id">;
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 <noreply@xtablo.com>`,
to: recipientmail,
subject: "Vous avez été invité à un tablo",
html: `<p>Vous avez été invité à un tablo avec <a href="${config.XTABLO_URL}/join/${tablo.name}?token=${token}">ce lien</a></p>`,
});
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" });
});

View file

@ -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 <noreply@xtablo.com>`,
to: recipientmail,
subject: "Vous avez été invité à un tablo",
html: `<p>Vous avez été invité à un tablo avec <a href="${config.XTABLO_URL}/join/${tablo.name}?token=${token}">ce lien</a></p>`,
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("");

View file

@ -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

View file

@ -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)';

View file

@ -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<Record<string, string>>({});
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<Record<string, string>>({});
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: () => {

View file

@ -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<TabloInsert, "owner_id">) => {
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",
});
},
});
};

View file

@ -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 <></>;
};

View file

@ -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<User | null>({
const { data: user, isPending } = useQuery<User | null>({
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<User>("/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 <LoadingSpinner />;
}
if (!data) {
if (!user) {
return children;
}
const store = createStore<User>()(() => data);
const store = createStore<User>()(() => user);
return (
<UserStoreContext.Provider value={store as StoreApi<User>}>

View file

@ -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<keyof Database, "public">]
type DefaultSchema = Database[Extract<keyof Database, "public">];
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;