feat: add subjects and tasks API, hooks, and related types

- Implemented subjects API with functions for creating, updating, and retrieving subjects, including history and bibliography.
- Added tasks API for managing user tasks, including listing and marking tasks as completed.
- Created hooks for managing AI interactions, authentication, subjects, tasks, and metadata queries.
- Established query keys for caching and managing query states.
- Introduced Supabase client and environment variable management for better configuration.
- Defined types for database and domain models to ensure type safety across the application.
This commit is contained in:
2026-01-09 09:00:33 -06:00
parent 8704b63b46
commit 65a73ca99f
25 changed files with 1819 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
import { createClient, type SupabaseClient } from "@supabase/supabase-js";
import type { Database } from "../types/database";
import { getEnv } from "./env";
let _client: SupabaseClient<Database> | null = null;
export function supabaseBrowser(): SupabaseClient<Database> {
if (_client) return _client;
const url = getEnv(
"VITE_SUPABASE_URL",
"NEXT_PUBLIC_SUPABASE_URL",
"SUPABASE_URL"
);
const anonKey = getEnv(
"VITE_SUPABASE_ANON_KEY",
"NEXT_PUBLIC_SUPABASE_ANON_KEY",
"SUPABASE_ANON_KEY"
);
_client = createClient<Database>(url, anonKey, {
auth: {
persistSession: true,
autoRefreshToken: true,
detectSessionInUrl: true,
},
});
return _client;
}

17
src/data/supabase/env.ts Normal file
View File

@@ -0,0 +1,17 @@
export function getEnv(...keys: string[]): string {
for (const key of keys) {
const fromProcess =
typeof process !== "undefined" ? (process as any).env?.[key] : undefined;
// Vite / bundlers
const fromImportMeta =
typeof import.meta !== "undefined" ? (import.meta as any).env?.[key] : undefined;
const value = fromProcess ?? fromImportMeta;
if (typeof value === "string" && value.trim().length > 0) return value.trim();
}
throw new Error(
`Falta variable de entorno. Probé: ${keys.join(", ")}`
);
}

View File

@@ -0,0 +1,47 @@
import type { SupabaseClient } from "@supabase/supabase-js";
import type { Database } from "../types/database";
import { supabaseBrowser } from "./client";
export type EdgeInvokeOptions = {
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
headers?: Record<string, string>;
};
export class EdgeFunctionError extends Error {
constructor(
message: string,
public readonly functionName: string,
public readonly status?: number,
public readonly details?: unknown
) {
super(message);
this.name = "EdgeFunctionError";
}
}
export async function invokeEdge<TOut>(
functionName: string,
body?: unknown,
opts: EdgeInvokeOptions = {},
client?: SupabaseClient<Database>
): Promise<TOut> {
const supabase = client ?? supabaseBrowser();
const { data, error } = await supabase.functions.invoke(functionName, {
body,
method: opts.method ?? "POST",
headers: opts.headers,
});
if (error) {
const anyErr = error as any;
throw new EdgeFunctionError(
anyErr.message ?? "Error en Edge Function",
functionName,
anyErr.status,
anyErr
);
}
return data as TOut;
}