diff --git a/api/src/__tests__/slots.test.ts b/api/src/__tests__/slots.test.ts index 8e3cce5..6ed184d 100644 --- a/api/src/__tests__/slots.test.ts +++ b/api/src/__tests__/slots.test.ts @@ -734,10 +734,8 @@ describe("generateTimeSlots", () => { expect(slot09_00?.available, "09:00 should be available").to.be.true; expect(slot09_30?.available, "09:30 should not be available").to.be.false; - expect(slot10_00?.available, "10:00 should not be unavailable").to.be - .false; // Within buffered time - expect(slot10_30?.available, "10:30 should not be unavailable").to.be - .false; // Within buffered time + expect(slot10_00?.available, "10:00 should not be unavailable").to.be.false; // Within buffered time + expect(slot10_30?.available, "10:30 should not be unavailable").to.be.false; // Within buffered time expect(slot11_00?.available, "11:00 should be available").to.be.true; // After buffered time }); @@ -1145,12 +1143,8 @@ describe("generateTimeSlots", () => { }); it("should format date strings correctly", () => { - expect(getDateStringCET(new Date("2024-01-15T10:30:00Z"))).to.equal( - "2024-01-15" - ); - expect(getDateStringCET(new Date("2024-12-31T23:59:59Z"))).to.equal( - "2025-01-01" - ); + expect(getDateStringCET(new Date("2024-01-15T10:30:00Z"))).to.equal("2024-01-15"); + expect(getDateStringCET(new Date("2024-12-31T23:59:59Z"))).to.equal("2025-01-01"); }); }); }); diff --git a/api/src/config.ts b/api/src/config.ts index f2d688e..1506488 100644 --- a/api/src/config.ts +++ b/api/src/config.ts @@ -47,38 +47,20 @@ function createConfig(): AppConfig { process.env.SUPABASE_SERVICE_ROLE_KEY ), SUPABASE_CONNECTION_STRING: process.env.SUPABASE_CONNECTION_STRING || "", - STREAM_CHAT_API_KEY: validateEnvVar( - "STREAM_CHAT_API_KEY", - process.env.STREAM_CHAT_API_KEY - ), + STREAM_CHAT_API_KEY: validateEnvVar("STREAM_CHAT_API_KEY", process.env.STREAM_CHAT_API_KEY), STREAM_CHAT_API_SECRET: validateEnvVar( "STREAM_CHAT_API_SECRET", process.env.STREAM_CHAT_API_SECRET ), EMAIL_USER: validateEnvVar("EMAIL_USER", process.env.EMAIL_USER), - EMAIL_CLIENT_ID: validateEnvVar( - "EMAIL_CLIENT_ID", - process.env.EMAIL_CLIENT_ID - ), - EMAIL_CLIENT_SECRET: validateEnvVar( - "EMAIL_CLIENT_SECRET", - process.env.EMAIL_CLIENT_SECRET - ), - EMAIL_REFRESH_TOKEN: validateEnvVar( - "EMAIL_REFRESH_TOKEN", - process.env.EMAIL_REFRESH_TOKEN - ), + EMAIL_CLIENT_ID: validateEnvVar("EMAIL_CLIENT_ID", process.env.EMAIL_CLIENT_ID), + EMAIL_CLIENT_SECRET: validateEnvVar("EMAIL_CLIENT_SECRET", process.env.EMAIL_CLIENT_SECRET), + EMAIL_REFRESH_TOKEN: validateEnvVar("EMAIL_REFRESH_TOKEN", process.env.EMAIL_REFRESH_TOKEN), CORS_ORIGIN: process.env.CORS_ORIGIN || "https://app.xtablo.com", XTABLO_URL: process.env.XTABLO_URL || "https://app.xtablo.com", R2_ACCOUNT_ID: validateEnvVar("R2_ACCOUNT_ID", process.env.R2_ACCOUNT_ID), - R2_ACCESS_KEY_ID: validateEnvVar( - "R2_ACCESS_KEY_ID", - process.env.R2_ACCESS_KEY_ID - ), - R2_SECRET_ACCESS_KEY: validateEnvVar( - "R2_SECRET_ACCESS_KEY", - process.env.R2_SECRET_ACCESS_KEY - ), + R2_ACCESS_KEY_ID: validateEnvVar("R2_ACCESS_KEY_ID", process.env.R2_ACCESS_KEY_ID), + R2_SECRET_ACCESS_KEY: validateEnvVar("R2_SECRET_ACCESS_KEY", process.env.R2_SECRET_ACCESS_KEY), SYNC_CALS_SECRET: process.env.SYNC_CALS_SECRET || "", LOG_LEVEL: "info", }; diff --git a/api/src/database.types.ts b/api/src/database.types.ts index eeced73..aa1513e 100644 --- a/api/src/database.types.ts +++ b/api/src/database.types.ts @@ -1,632 +1,624 @@ -export type Json = - | string - | number - | boolean - | null - | { [key: string]: Json | undefined } - | Json[] +export type Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[]; export type Database = { // Allows to automatically instantiate createClient with right options // instead of createClient(URL, KEY) __InternalSupabase: { - PostgrestVersion: "13.0.4" - } + PostgrestVersion: "13.0.4"; + }; public: { Tables: { availabilities: { Row: { - availability_data: Json - created_at: string - exceptions: Json | null - id: number - updated_at: string - user_id: string - } + availability_data: Json; + created_at: string; + exceptions: Json | null; + id: number; + updated_at: string; + user_id: string; + }; Insert: { - availability_data?: Json - created_at?: string - exceptions?: Json | null - id?: number - updated_at?: string - user_id: string - } + availability_data?: Json; + created_at?: string; + exceptions?: Json | null; + id?: number; + updated_at?: string; + user_id: string; + }; Update: { - availability_data?: Json - created_at?: string - exceptions?: Json | null - id?: number - updated_at?: string - user_id?: string - } - Relationships: [] - } + availability_data?: Json; + created_at?: string; + exceptions?: Json | null; + id?: number; + updated_at?: string; + user_id?: string; + }; + Relationships: []; + }; calendar_subscriptions: { Row: { - created_at: string | null - id: string - tablo_id: string - token: string - } + created_at: string | null; + id: string; + tablo_id: string; + token: string; + }; Insert: { - created_at?: string | null - id?: string - tablo_id: string - token: string - } + created_at?: string | null; + id?: string; + tablo_id: string; + token: string; + }; Update: { - created_at?: string | null - id?: string - tablo_id?: string - token?: string - } + created_at?: string | null; + id?: string; + tablo_id?: string; + token?: string; + }; Relationships: [ { - foreignKeyName: "calendar_subscriptions_tablo_id_fkey" - columns: ["tablo_id"] - isOneToOne: true - referencedRelation: "events_and_tablos" - referencedColumns: ["tablo_id"] + foreignKeyName: "calendar_subscriptions_tablo_id_fkey"; + columns: ["tablo_id"]; + isOneToOne: true; + referencedRelation: "events_and_tablos"; + referencedColumns: ["tablo_id"]; }, { - foreignKeyName: "calendar_subscriptions_tablo_id_fkey" - columns: ["tablo_id"] - isOneToOne: true - referencedRelation: "tablos" - referencedColumns: ["id"] + foreignKeyName: "calendar_subscriptions_tablo_id_fkey"; + columns: ["tablo_id"]; + isOneToOne: true; + referencedRelation: "tablos"; + referencedColumns: ["id"]; }, { - foreignKeyName: "calendar_subscriptions_tablo_id_fkey" - columns: ["tablo_id"] - isOneToOne: true - referencedRelation: "user_tablos" - referencedColumns: ["id"] + foreignKeyName: "calendar_subscriptions_tablo_id_fkey"; + columns: ["tablo_id"]; + isOneToOne: true; + referencedRelation: "user_tablos"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; 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: []; + }; event_types: { Row: { - config: Json - created_at: string | null - deleted_at: string | null - id: string - is_active: boolean - standard_name: string | null - updated_at: string | null - user_id: string - } + config: Json; + created_at: string | null; + deleted_at: string | null; + id: string; + is_active: boolean; + standard_name: string | null; + updated_at: string | null; + user_id: string; + }; Insert: { - config?: Json - created_at?: string | null - deleted_at?: string | null - id?: string - is_active?: boolean - standard_name?: string | null - updated_at?: string | null - user_id: string - } + config?: Json; + created_at?: string | null; + deleted_at?: string | null; + id?: string; + is_active?: boolean; + standard_name?: string | null; + updated_at?: string | null; + user_id: string; + }; Update: { - config?: Json - created_at?: string | null - deleted_at?: string | null - id?: string - is_active?: boolean - standard_name?: string | null - updated_at?: string | null - user_id?: string - } - Relationships: [] - } + config?: Json; + created_at?: string | null; + deleted_at?: string | null; + id?: string; + is_active?: boolean; + standard_name?: string | null; + updated_at?: string | null; + user_id?: string; + }; + Relationships: []; + }; events: { Row: { - created_at: string | null - created_by: string - deleted_at: string | null - description: string | null - end_time: string | null - id: string - start_date: string - start_time: string - tablo_id: string - title: string - } + created_at: string | null; + created_by: string; + deleted_at: string | null; + description: string | null; + end_time: string | null; + id: string; + start_date: string; + start_time: string; + tablo_id: string; + title: string; + }; Insert: { - created_at?: string | null - created_by: string - deleted_at?: string | null - description?: string | null - end_time?: string | null - id?: string - start_date: string - start_time: string - tablo_id: string - title: string - } + created_at?: string | null; + created_by: string; + deleted_at?: string | null; + description?: string | null; + end_time?: string | null; + id?: string; + start_date: string; + start_time: string; + tablo_id: string; + title: string; + }; Update: { - created_at?: string | null - created_by?: string - deleted_at?: string | null - description?: string | null - end_time?: string | null - id?: string - start_date?: string - start_time?: string - tablo_id?: string - title?: string - } + created_at?: string | null; + created_by?: string; + deleted_at?: string | null; + description?: string | null; + end_time?: string | null; + id?: string; + start_date?: string; + start_time?: string; + tablo_id?: string; + title?: string; + }; Relationships: [ { - foreignKeyName: "fk_events_tablo_id" - columns: ["tablo_id"] - isOneToOne: false - referencedRelation: "events_and_tablos" - referencedColumns: ["tablo_id"] + foreignKeyName: "fk_events_tablo_id"; + columns: ["tablo_id"]; + isOneToOne: false; + referencedRelation: "events_and_tablos"; + referencedColumns: ["tablo_id"]; }, { - foreignKeyName: "fk_events_tablo_id" - columns: ["tablo_id"] - isOneToOne: false - referencedRelation: "tablos" - referencedColumns: ["id"] + foreignKeyName: "fk_events_tablo_id"; + columns: ["tablo_id"]; + isOneToOne: false; + referencedRelation: "tablos"; + referencedColumns: ["id"]; }, { - foreignKeyName: "fk_events_tablo_id" - columns: ["tablo_id"] - isOneToOne: false - referencedRelation: "user_tablos" - referencedColumns: ["id"] + foreignKeyName: "fk_events_tablo_id"; + columns: ["tablo_id"]; + isOneToOne: false; + referencedRelation: "user_tablos"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; 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 - first_name: string | null - id: string - is_temporary: boolean - last_name: string | null - name: string | null - short_user_id: string - } + avatar_url: string | null; + email: string | null; + first_name: string | null; + id: string; + is_temporary: boolean; + last_name: string | null; + name: string | null; + short_user_id: string; + }; Insert: { - avatar_url?: string | null - email?: string | null - first_name?: string | null - id: string - is_temporary?: boolean - last_name?: string | null - name?: string | null - short_user_id: string - } + avatar_url?: string | null; + email?: string | null; + first_name?: string | null; + id: string; + is_temporary?: boolean; + last_name?: string | null; + name?: string | null; + short_user_id: string; + }; Update: { - avatar_url?: string | null - email?: string | null - first_name?: string | null - id?: string - is_temporary?: boolean - last_name?: string | null - name?: string | null - short_user_id?: string - } - Relationships: [] - } + avatar_url?: string | null; + email?: string | null; + first_name?: string | null; + id?: string; + is_temporary?: boolean; + last_name?: string | null; + name?: string | null; + short_user_id?: string; + }; + Relationships: []; + }; tablo_access: { Row: { - created_at: string | null - granted_by: string - id: number - is_active: boolean | null - is_admin: boolean | null - tablo_id: string - user_id: string - } + created_at: string | null; + granted_by: string; + id: number; + is_active: boolean | null; + is_admin: boolean | null; + tablo_id: string; + user_id: string; + }; Insert: { - created_at?: string | null - granted_by: string - id?: number - is_active?: boolean | null - is_admin?: boolean | null - tablo_id: string - user_id: string - } + created_at?: string | null; + granted_by: string; + id?: number; + is_active?: boolean | null; + is_admin?: boolean | null; + tablo_id: string; + user_id: string; + }; Update: { - created_at?: string | null - granted_by?: string - id?: number - is_active?: boolean | null - is_admin?: boolean | null - tablo_id?: string - user_id?: string - } + created_at?: string | null; + granted_by?: string; + id?: number; + is_active?: boolean | null; + is_admin?: boolean | null; + tablo_id?: string; + user_id?: string; + }; Relationships: [ { - foreignKeyName: "fk_tablo_access_tablo_id" - columns: ["tablo_id"] - isOneToOne: false - referencedRelation: "events_and_tablos" - referencedColumns: ["tablo_id"] + foreignKeyName: "fk_tablo_access_tablo_id"; + columns: ["tablo_id"]; + isOneToOne: false; + referencedRelation: "events_and_tablos"; + referencedColumns: ["tablo_id"]; }, { - foreignKeyName: "fk_tablo_access_tablo_id" - columns: ["tablo_id"] - isOneToOne: false - referencedRelation: "tablos" - referencedColumns: ["id"] + foreignKeyName: "fk_tablo_access_tablo_id"; + columns: ["tablo_id"]; + isOneToOne: false; + referencedRelation: "tablos"; + referencedColumns: ["id"]; }, { - foreignKeyName: "fk_tablo_access_tablo_id" - columns: ["tablo_id"] - isOneToOne: false - referencedRelation: "user_tablos" - referencedColumns: ["id"] + foreignKeyName: "fk_tablo_access_tablo_id"; + columns: ["tablo_id"]; + isOneToOne: false; + referencedRelation: "user_tablos"; + referencedColumns: ["id"]; }, { - foreignKeyName: "fk_tablo_access_user_id_from_profiles" - columns: ["user_id"] - isOneToOne: false - referencedRelation: "profiles" - referencedColumns: ["id"] + foreignKeyName: "fk_tablo_access_user_id_from_profiles"; + columns: ["user_id"]; + isOneToOne: false; + referencedRelation: "profiles"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; tablo_invites: { Row: { - id: number - invite_token: string - invited_by: string - invited_email: string - tablo_id: string - } + 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: string - } + 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?: string - } + 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: "events_and_tablos" - referencedColumns: ["tablo_id"] + foreignKeyName: "fk_tablo_invitations_tablo_id"; + columns: ["tablo_id"]; + isOneToOne: false; + referencedRelation: "events_and_tablos"; + referencedColumns: ["tablo_id"]; }, { - 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"]; }, { - foreignKeyName: "fk_tablo_invitations_tablo_id" - columns: ["tablo_id"] - isOneToOne: false - referencedRelation: "user_tablos" - referencedColumns: ["id"] + foreignKeyName: "fk_tablo_invitations_tablo_id"; + columns: ["tablo_id"]; + isOneToOne: false; + referencedRelation: "user_tablos"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; tablos: { Row: { - 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 - } + 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?: string - 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?: string - 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: []; + }; user_introductions: { Row: { - config: Json - created_at: string | null - updated_at: string | null - user_id: string - } + config: Json; + created_at: string | null; + updated_at: string | null; + user_id: string; + }; Insert: { - config?: Json - created_at?: string | null - updated_at?: string | null - user_id: string - } + config?: Json; + created_at?: string | null; + updated_at?: string | null; + user_id: string; + }; Update: { - config?: Json - created_at?: string | null - updated_at?: string | null - user_id?: string - } - Relationships: [] - } - } + config?: Json; + created_at?: string | null; + updated_at?: string | null; + user_id?: string; + }; + Relationships: []; + }; + }; Views: { events_and_tablos: { Row: { - description: string | null - end_time: string | null - event_id: string | null - start_date: string | null - start_time: string | null - tablo_color: string | null - tablo_id: string | null - tablo_name: string | null - tablo_status: string | null - title: string | null - } - Relationships: [] - } + description: string | null; + end_time: string | null; + event_id: string | null; + start_date: string | null; + start_time: string | null; + tablo_color: string | null; + tablo_id: string | null; + tablo_name: string | null; + tablo_status: string | null; + title: string | null; + }; + Relationships: []; + }; user_tablos: { Row: { - access_level: string | null - color: string | null - created_at: string | null - deleted_at: string | null - id: string | null - image: string | null - is_admin: boolean | null - name: string | null - position: number | null - status: string | null - user_id: string | null - } + access_level: string | null; + color: string | null; + created_at: string | null; + deleted_at: string | null; + id: string | null; + image: string | null; + is_admin: boolean | null; + name: string | null; + position: number | null; + status: string | null; + user_id: string | null; + }; Relationships: [ { - foreignKeyName: "fk_tablo_access_user_id_from_profiles" - columns: ["user_id"] - isOneToOne: false - referencedRelation: "profiles" - referencedColumns: ["id"] + foreignKeyName: "fk_tablo_access_user_id_from_profiles"; + columns: ["user_id"]; + isOneToOne: false; + referencedRelation: "profiles"; + referencedColumns: ["id"]; }, - ] - } - } + ]; + }; + }; Functions: { generate_random_string: { - Args: { length?: number } - Returns: string - } - } + Args: { length?: number }; + Returns: string; + }; + }; Enums: { - devis_status: "draft" | "sent" | "accepted" | "rejected" | "expired" - } + devis_status: "draft" | "sent" | "accepted" | "rejected" | "expired"; + }; CompositeTypes: { time_range: { - start_time: string | null - end_time: string | null - } - } - } -} + start_time: string | null; + end_time: string | null; + }; + }; + }; +}; -type DatabaseWithoutInternals = Omit +type DatabaseWithoutInternals = Omit; -type DefaultSchema = DatabaseWithoutInternals[Extract] +type DefaultSchema = DatabaseWithoutInternals[Extract]; export type Tables< DefaultSchemaTableNameOrOptions extends | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) | { schema: keyof DatabaseWithoutInternals }, TableName extends DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals + schema: keyof DatabaseWithoutInternals; } ? keyof (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"]) : never = never, > = DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals + schema: keyof DatabaseWithoutInternals; } ? (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & DatabaseWithoutInternals[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 + : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) + ? (DefaultSchema["Tables"] & DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { + Row: infer R; } ? R : never - : never + : never; export type TablesInsert< DefaultSchemaTableNameOrOptions extends | keyof DefaultSchema["Tables"] | { schema: keyof DatabaseWithoutInternals }, TableName extends DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals + schema: keyof DatabaseWithoutInternals; } ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] : never = never, > = DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals + schema: keyof DatabaseWithoutInternals; } ? DatabaseWithoutInternals[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 + Insert: infer I; } ? I : never - : never + : never; export type TablesUpdate< DefaultSchemaTableNameOrOptions extends | keyof DefaultSchema["Tables"] | { schema: keyof DatabaseWithoutInternals }, TableName extends DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals + schema: keyof DatabaseWithoutInternals; } ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] : never = never, > = DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals + schema: keyof DatabaseWithoutInternals; } ? DatabaseWithoutInternals[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 + Update: infer U; } ? U : never - : never + : never; export type Enums< DefaultSchemaEnumNameOrOptions extends | keyof DefaultSchema["Enums"] | { schema: keyof DatabaseWithoutInternals }, EnumName extends DefaultSchemaEnumNameOrOptions extends { - schema: keyof DatabaseWithoutInternals + schema: keyof DatabaseWithoutInternals; } ? keyof DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"] : never = never, > = DefaultSchemaEnumNameOrOptions extends { - schema: keyof DatabaseWithoutInternals + schema: keyof DatabaseWithoutInternals; } ? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName] : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] - : never + : never; export type CompositeTypes< PublicCompositeTypeNameOrOptions extends | keyof DefaultSchema["CompositeTypes"] | { schema: keyof DatabaseWithoutInternals }, CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { - schema: keyof DatabaseWithoutInternals + schema: keyof DatabaseWithoutInternals; } ? keyof DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"] : never = never, > = PublicCompositeTypeNameOrOptions extends { - schema: keyof DatabaseWithoutInternals + schema: keyof DatabaseWithoutInternals; } ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName] : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"] ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] - : never + : never; export const Constants = { public: { @@ -634,4 +626,4 @@ export const Constants = { devis_status: ["draft", "sent", "accepted", "rejected", "expired"], }, }, -} as const +} as const; diff --git a/api/src/helpers.ts b/api/src/helpers.ts index 444c146..1a52591 100644 --- a/api/src/helpers.ts +++ b/api/src/helpers.ts @@ -1,8 +1,4 @@ -import { - ListObjectsV2Command, - PutObjectCommand, - S3Client, -} from "@aws-sdk/client-s3"; +import { ListObjectsV2Command, PutObjectCommand, S3Client } from "@aws-sdk/client-s3"; import type { SupabaseClient } from "@supabase/supabase-js"; import type { EventAndTablo } from "./types.ts"; @@ -54,14 +50,10 @@ export const generateICSFromEvents = ( `DTSTART:${startDateTime}`, `DTEND:${endDateTime}`, `SUMMARY:${escapeICSText(event.title)}`, - `DESCRIPTION:${escapeICSText( - `Tablo: ${event.tablo_name}\n${event.description || ""}` - )}`, + `DESCRIPTION:${escapeICSText(`Tablo: ${event.tablo_name}\n${event.description || ""}`)}`, event.tablo_name ? `CATEGORIES:${escapeICSText(event.tablo_name)}` : "", `CREATED:${new Date().toISOString().replace(/[-:]/g, "").split(".")[0]}Z`, - `LAST-MODIFIED:${ - new Date().toISOString().replace(/[-:]/g, "").split(".")[0] - }Z`, + `LAST-MODIFIED:${new Date().toISOString().replace(/[-:]/g, "").split(".")[0]}Z`, "STATUS:CONFIRMED", "TRANSP:OPAQUE", "END:VEVENT", @@ -112,10 +104,7 @@ export const writeCalendarFileToR2 = async ( ); }; -export const getTabloFileNames = async ( - s3_client: S3Client, - tabloId: string -) => { +export const getTabloFileNames = async (s3_client: S3Client, tabloId: string) => { const bucketName = "tablo-data"; const { Contents } = await s3_client.send( @@ -130,11 +119,7 @@ export const getTabloFileNames = async ( ); }; -export const isTabloMember = async ( - supabase: SupabaseClient, - tabloId: string, - userId: string -) => { +export const isTabloMember = async (supabase: SupabaseClient, tabloId: string, userId: string) => { const { data: tabloAccess, error: isMemberError } = await supabase .from("tablo_access") .select("*") @@ -149,11 +134,7 @@ export const isTabloMember = async ( return tabloAccess?.length > 0; }; -export const isTabloAdmin = async ( - supabase: SupabaseClient, - tabloId: string, - userId: string -) => { +export const isTabloAdmin = async (supabase: SupabaseClient, tabloId: string, userId: string) => { const { data: tabloAccess, error: isAdminError } = await supabase .from("tablo_access") .select("*") diff --git a/api/src/public.ts b/api/src/public.ts index 3ad5668..9b8bae4 100644 --- a/api/src/public.ts +++ b/api/src/public.ts @@ -51,25 +51,22 @@ publicRouter.get("/slots/:shortUserId/:standardName", async (c) => { return c.json({ error: "Event type not found" }, 404); } - const eventType = - eventTypeData as Database["public"]["Tables"]["event_types"]["Row"]; + const eventType = eventTypeData as Database["public"]["Tables"]["event_types"]["Row"]; const eventTypeConfig = eventType.config as EventTypeConfig; // Get user's availabilities - const { data: availabilitiesData, error: availabilitiesError } = - await supabase - .from("availabilities") - .select("*") - .eq("user_id", user.id) - .single(); + const { data: availabilitiesData, error: availabilitiesError } = await supabase + .from("availabilities") + .select("*") + .eq("user_id", user.id) + .single(); if (availabilitiesError) { return c.json({ error: "Availabilities not found" }, 404); } const availabilities = availabilitiesData as Tables<"availabilities">; - const weeklyAvailability = - availabilities.availability_data as WeeklyAvailability; + const weeklyAvailability = availabilities.availability_data as WeeklyAvailability; const exceptions = (availabilities.exceptions as Exception[]) || []; // Get existing events for the next month diff --git a/api/src/slots.ts b/api/src/slots.ts index 7e5d5e4..588d214 100644 --- a/api/src/slots.ts +++ b/api/src/slots.ts @@ -1,6 +1,5 @@ -import type { Tables } from "./database.types.js"; - import { DateTime } from "luxon"; +import type { Tables } from "./database.types.js"; // Types for availability calculation type TimeRange = { @@ -57,9 +56,7 @@ function parseTime(timeStr: string): { hours: number; minutes: number } { } function formatTime(hours: number, minutes: number): string { - return `${hours.toString().padStart(2, "0")}:${minutes - .toString() - .padStart(2, "0")}`; + return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`; } function addMinutes(timeStr: string, minutesToAdd: number): string { @@ -82,9 +79,7 @@ function mergeOverlappingTimeRanges(ranges: TimeRange[]): TimeRange[] { if (ranges.length <= 1) return ranges; // Sort ranges by start time - const sortedRanges = [...ranges].sort((a, b) => - a.start.localeCompare(b.start) - ); + const sortedRanges = [...ranges].sort((a, b) => a.start.localeCompare(b.start)); const merged: TimeRange[] = [sortedRanges[0]]; for (let i = 1; i < sortedRanges.length; i++) { @@ -94,8 +89,7 @@ function mergeOverlappingTimeRanges(ranges: TimeRange[]): TimeRange[] { // Check if current range overlaps with the last merged range if (current.start <= lastMerged.end) { // Merge by extending the end time if current range extends further - lastMerged.end = - current.end > lastMerged.end ? current.end : lastMerged.end; + lastMerged.end = current.end > lastMerged.end ? current.end : lastMerged.end; } else { // No overlap, add current range to merged array merged.push(current); @@ -144,9 +138,7 @@ function getMinAdvanceBookingDate( } export function getDateStringCET(date: Date): string { - return DateTime.fromJSDate(date) - .setZone("Europe/Paris") - .toFormat("yyyy-MM-dd"); + return DateTime.fromJSDate(date).setZone("Europe/Paris").toFormat("yyyy-MM-dd"); } export function generateTimeSlots( @@ -183,10 +175,7 @@ export function generateTimeSlots( } // Check minimum advance booking - const minAdvanceBooking = getMinAdvanceBookingDate( - eventTypeConfig, - currentTime - ); + const minAdvanceBooking = getMinAdvanceBookingDate(eventTypeConfig, currentTime); // Generate slots for each time range for (const range of timeRanges) { @@ -197,17 +186,13 @@ export function generateTimeSlots( const endMinutes = endTime.hours * 60 + endTime.minutes; while (currentMinutes + eventTypeConfig.duration <= endMinutes) { - const slotTime = formatTime( - Math.floor(currentMinutes / 60), - currentMinutes % 60 - ); + const slotTime = formatTime(Math.floor(currentMinutes / 60), currentMinutes % 60); // Check if slot is in the future (considering minimum advance booking) // Compare dates first, then times if on the same date const isInFuture = dateStr > minAdvanceBooking.date || - (dateStr === minAdvanceBooking.date && - slotTime >= minAdvanceBooking.time); + (dateStr === minAdvanceBooking.date && slotTime >= minAdvanceBooking.time); slots.push({ date: dateStr, @@ -233,8 +218,7 @@ export function generateTimeSlots( if (event.start_date !== dateStr || event.deleted_at) return false; const eventStart = event.start_time; - const eventEnd = - event.end_time || addMinutes(eventStart, eventTypeConfig.duration); + const eventEnd = event.end_time || addMinutes(eventStart, eventTypeConfig.duration); // Apply buffer time around the existing event const bufferedEventStart = addMinutes(eventStart, -bufferTime); diff --git a/api/src/tablo.ts b/api/src/tablo.ts index 3ee35f0..bb5488e 100644 --- a/api/src/tablo.ts +++ b/api/src/tablo.ts @@ -6,11 +6,7 @@ import type { StreamChat } from "stream-chat"; import { config } from "./config.js"; import type { Tables } from "./database.types.ts"; import { writeCalendarFileToR2 } from "./helpers.js"; -import { - authMiddleware, - r2Middleware, - streamChatMiddleware, -} from "./middleware.js"; +import { authMiddleware, r2Middleware, streamChatMiddleware } from "./middleware.js"; import { generateToken } from "./token.js"; import { transporter } from "./transporter.js"; import type { EventInsertInTablo, TabloInsert } from "./types.ts"; @@ -170,9 +166,7 @@ tabloRouter.post("/create-and-invite", async (c) => { const { data: insertedTablo, error } = await supabase .from("tablos") .insert({ - name: `${invitedUserDataTyped.name || "Invité"} / ${ - ownerDataTyped.name || "Propriétaire" - }`, + name: `${invitedUserDataTyped.name || "Invité"} / ${ownerDataTyped.name || "Propriétaire"}`, color: "bg-blue-500", status: "todo", owner_id: ownerId, @@ -190,22 +184,20 @@ tabloRouter.post("/create-and-invite", async (c) => { } // Grant access to the current user (invited user) as a non-admin member - const { error: tabloAccessError } = await supabase - .from("tablo_access") - .insert( - { - tablo_id: tabloData.id, - user_id: user.id, - // ** IMPORTANT ** - is_admin: false, - // ------------- - is_active: true, - granted_by: ownerId, - } - // { - // onConflict: "tablo_id, user_id", - // } - ); + const { error: tabloAccessError } = await supabase.from("tablo_access").insert( + { + tablo_id: tabloData.id, + user_id: user.id, + // ** IMPORTANT ** + is_admin: false, + // ------------- + is_active: true, + granted_by: ownerId, + } + // { + // onConflict: "tablo_id, user_id", + // } + ); if (tabloAccessError) { console.error("tabloAccessError", tabloAccessError); @@ -298,8 +290,7 @@ tabloRouter.patch("/update", async (c) => { const updatedTablo = update as Tables<"tablos">; - const isUpdatingName = - tablo.name !== undefined && tablo.name !== updatedTablo.name; + const isUpdatingName = tablo.name !== undefined && tablo.name !== updatedTablo.name; if (error) { return c.json({ error: error.message }, 500); @@ -344,10 +335,7 @@ tabloRouter.delete("/delete", async (c) => { .single(); if (accessError || !tabloAccess || !tabloAccess.is_admin) { - return c.json( - { error: "You are not authorized to delete this tablo" }, - 403 - ); + return c.json({ error: "You are not authorized to delete this tablo" }, 403); } if (error) { @@ -388,10 +376,7 @@ tabloRouter.post("/invite", async (c) => { } if (tablo.owner_id !== sender.id) { - return c.json( - { error: "You are not allowed to invite users to this tablo" }, - 400 - ); + return c.json({ error: "You are not allowed to invite users to this tablo" }, 400); } const { data: introConfigData, error: introError } = await supabase @@ -425,8 +410,8 @@ tabloRouter.post("/invite", async (c) => {

Cliquez sur ce lien pour accepter l'invitation.

+ token + )}">ce lien pour accepter l'invitation.


Cordialement.

`, @@ -462,17 +447,15 @@ tabloRouter.post("/join", async (c) => { const { id: invite_id, tablo_id, invited_by } = inviteData; - 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, - }); + 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) { console.error("tabloAccessError", tabloAccessError); diff --git a/api/src/tablo_data.ts b/api/src/tablo_data.ts index aee1ea0..22b25cd 100644 --- a/api/src/tablo_data.ts +++ b/api/src/tablo_data.ts @@ -2,11 +2,7 @@ import { PutObjectCommand, type S3Client } from "@aws-sdk/client-s3"; import type { SupabaseClient, User } from "@supabase/supabase-js"; import { type Context, Hono, type Next } from "hono"; import { getTabloFileNames, isTabloAdmin, isTabloMember } from "./helpers.js"; -import { - authMiddleware, - r2Middleware, - streamChatMiddleware, -} from "./middleware.js"; +import { authMiddleware, r2Middleware, streamChatMiddleware } from "./middleware.js"; export const tabloDataRouter = new Hono<{ Variables: { diff --git a/api/src/tasks.ts b/api/src/tasks.ts index 4aa3e87..021471e 100644 --- a/api/src/tasks.ts +++ b/api/src/tasks.ts @@ -34,7 +34,7 @@ taskRouter.post("/sync-calendars", async (c) => { token: string; tablo_id: string; tablos: { name: string }; - } + }, ]; calendarSubscriptionsData.forEach(async (subscription) => { diff --git a/api/src/user.ts b/api/src/user.ts index de84243..4cf86a3 100644 --- a/api/src/user.ts +++ b/api/src/user.ts @@ -1,20 +1,16 @@ -import type { SupabaseClient, User } from "@supabase/supabase-js"; -import { Hono } from "hono"; -import type { Transporter } from "nodemailer"; -import { StreamChat } from "stream-chat"; -import type { Tables } from "./database.types.ts"; -import { - authMiddleware, - r2Middleware, - streamChatMiddleware, -} from "./middleware.js"; -import { transporter } from "./transporter.js"; import { DeleteObjectsCommand, ListObjectsV2Command, PutObjectCommand, type S3Client, } from "@aws-sdk/client-s3"; +import type { SupabaseClient, User } from "@supabase/supabase-js"; +import { Hono } from "hono"; +import type { Transporter } from "nodemailer"; +import { StreamChat } from "stream-chat"; +import type { Tables } from "./database.types.ts"; +import { authMiddleware, r2Middleware, streamChatMiddleware } from "./middleware.js"; +import { transporter } from "./transporter.js"; export const userRouter = new Hono<{ Variables: { @@ -34,11 +30,7 @@ userRouter.post("/sign-up-to-stream", async (c) => { const { id } = c.get("user"); const supabase = c.get("supabase"); - const { data } = await supabase - .from("profiles") - .select("*") - .eq("id", id) - .single(); + const { data } = await supabase.from("profiles").select("*").eq("id", id).single(); const user = data as Tables<"profiles">; @@ -59,11 +51,7 @@ userRouter.get("/me", async (c) => { const supabase = c.get("supabase"); const streamServerClient = c.get("streamServerClient"); - const { data, error } = await supabase - .from("profiles") - .select("*") - .eq("id", user.id) - .single(); + const { data, error } = await supabase.from("profiles").select("*").eq("id", user.id).single(); const userData = data as Tables<"profiles">; @@ -212,9 +200,7 @@ userRouter.post("/profile/avatar", async (c) => { const randomString = Math.random().toString(36).substring(2, 15); const base64Content = Buffer.from(content, "base64"); - const key = `${user.id}/public_avatar_${randomString}.${ - contentType.split("/")[1] - }`; + const key = `${user.id}/public_avatar_${randomString}.${contentType.split("/")[1]}`; try { await s3Client.send( @@ -263,8 +249,7 @@ userRouter.delete("/profile/avatar", async (c) => { }) ); - if (listedObjects.Contents.length === 0) - return c.json({ error: "No objects found" }, 404); + if (listedObjects.Contents.length === 0) return c.json({ error: "No objects found" }, 404); await s3Client.send( new DeleteObjectsCommand({