Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions apps/website/app/api/supabase/insert/account/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { createClient } from "~/utils/supabase/server";
import { NextResponse, NextRequest } from "next/server";
import {
getOrCreateEntity,
GetOrCreateEntityResult,
} from "~/utils/supabase/dbUtils";
import {
createApiResponse,
handleRouteError,
defaultOptionsHandler,
} from "~/utils/supabase/apiUtils";
import { Tables, TablesInsert } from "~/utils/supabase/types.gen";

type AccountDataInput = TablesInsert<"Account">;
type AccountRecord = Tables<"Account">;

const getOrCreateAccount = async (
supabasePromise: ReturnType<typeof createClient>,
accountData: AccountDataInput,
): Promise<GetOrCreateEntityResult<AccountRecord>> => {
const {
person_id,
platform_id,
active = true,
write_permission = true,
} = accountData;

if (
person_id === undefined ||
person_id === null ||
platform_id === undefined ||
platform_id === null
) {
return {
entity: null,
error: "Missing required fields: person_id or platform_id.",
details: "Both person_id and platform_id are required.",
created: false,
status: 400,
};
}

const supabase = await supabasePromise;

const result = await getOrCreateEntity<"Account">(
supabase,
"Account",
"id, person_id, platform_id, active, write_permission",
{ person_id: person_id, platform_id: platform_id },
{ person_id, platform_id, active, write_permission },
"Account",
);

if (
result.error &&
result.details &&
result.status === 400 &&
result.details.includes("violates foreign key constraint")
) {
if (result.details.includes("Account_person_id_fkey")) {
return {
...result,
error: `Invalid person_id: No Person record found for ID ${person_id}.`,
};
} else if (result.details.includes("Account_platform_id_fkey")) {
return {
...result,
error: `Invalid platform_id: No Space record found for ID ${platform_id}.`,
};
}
}
return result;
};

export const POST = async (request: NextRequest): Promise<NextResponse> => {
const supabasePromise = createClient();

try {
const body: AccountDataInput = await request.json();

if (body.person_id === undefined || body.person_id === null) {
return createApiResponse(request, {
error: "Missing or invalid person_id.",
status: 400,
});
}
if (body.platform_id === undefined || body.platform_id === null) {
return createApiResponse(request, {
error: "Missing or invalid platform_id.",
status: 400,
});
}

const result = await getOrCreateAccount(supabasePromise, body);

return createApiResponse(request, {
data: result.entity,
error: result.error,
details: result.details,
status: result.status,
created: result.created,
});
} catch (e: unknown) {
return handleRouteError(request, e, "/api/supabase/insert/account");
}
};

export const OPTIONS = defaultOptionsHandler;
71 changes: 71 additions & 0 deletions apps/website/app/api/supabase/insert/agents/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { createClient } from "~/utils/supabase/server";
import { NextResponse, NextRequest } from "next/server";
import {
getOrCreateEntity,
GetOrCreateEntityResult,
} from "~/utils/supabase/dbUtils";
import {
createApiResponse,
handleRouteError,
defaultOptionsHandler,
} from "~/utils/supabase/apiUtils";
import { Database, Tables, TablesInsert } from "~/utils/supabase/types.gen";

type AgentDataInput = TablesInsert<"Agent">;
type AgentRecord = Tables<"Agent">;

const getOrCreateAgentByType = async (
supabasePromise: ReturnType<typeof createClient>,
agentType: Database["public"]["Enums"]["EntityType"],
): Promise<GetOrCreateEntityResult<AgentRecord>> => {
if (!agentType) {
return {
entity: null,
error: "Missing or invalid 'type' for Agent.",
details: "Agent 'type' is required and cannot be empty.",
created: false,
status: 400,
};
}

const supabase = await supabasePromise;

return getOrCreateEntity<"Agent">(
supabase,
"Agent",
"id, type",
{ type: agentType },
{ type: agentType },
"Agent",
);
};

export const POST = async (request: NextRequest): Promise<NextResponse> => {
const supabasePromise = createClient();

try {
const body: AgentDataInput = await request.json();
const { type } = body;

if (!type || typeof type !== "string" || type.trim() === "") {
return createApiResponse(request, {
error: "Validation Error: Missing or invalid type for Agent.",
status: 400,
});
}

const result = await getOrCreateAgentByType(supabasePromise, type);

return createApiResponse(request, {
data: result.entity,
error: result.error,
details: result.details,
status: result.status,
created: result.created,
});
} catch (e: unknown) {
return handleRouteError(request, e, "/api/supabase/insert/agents");
}
};

export const OPTIONS = defaultOptionsHandler;
105 changes: 105 additions & 0 deletions apps/website/app/api/supabase/insert/content-embedding/batch/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { createClient } from "~/utils/supabase/server";
import { NextResponse, NextRequest } from "next/server";
import {
createApiResponse,
handleRouteError,
defaultOptionsHandler,
} from "~/utils/supabase/apiUtils";
import {
processAndInsertBatch,
BatchProcessResult,
} from "~/utils/supabase/dbUtils";
import {
inputProcessing,
outputProcessing,
type ApiInputEmbeddingItem,
type ApiOutputEmbeddingRecord,
} from "../route";

const batchInsertEmbeddingsProcess = async (
supabase: Awaited<ReturnType<typeof createClient>>,
embeddingItems: ApiInputEmbeddingItem[],
): Promise<BatchProcessResult<ApiOutputEmbeddingRecord>> => {
// groupBy is node21 only. Group by model.
// Note: This means that later index values may be totally wrong.
const by_model: { [key: string]: ApiInputEmbeddingItem[] } = {};
for (let i = 0; i < embeddingItems.length; i++) {
const inputItem = embeddingItems[i];
if (inputItem !== undefined && inputItem.model !== undefined) {
if (by_model[inputItem.model] === undefined) {
by_model[inputItem.model] = [inputItem];
} else {
by_model[inputItem.model]!.push(inputItem);
}
} else {
return {
status: 400,
error: `Element ${i} undefined or does not have a model`,
};
}
}
const globalResults: ApiOutputEmbeddingRecord[] = [];
const partial_errors = [];
let created = true; // TODO: Maybe transmit from below
for (const table_name of Object.keys(by_model)) {
const embeddingItemsSet = by_model[table_name];
const results = await processAndInsertBatch<
"ContentEmbedding_openai_text_embedding_3_small_1536",
ApiInputEmbeddingItem,
ApiOutputEmbeddingRecord
>(
supabase,
embeddingItemsSet!,
table_name,
"*", // Select all fields, adjust if needed for ContentEmbeddingRecord
"ContentEmbedding",
inputProcessing!,
outputProcessing,
);
if (results.error || results.data === undefined)
return { ...results, data: undefined };
globalResults.push(...results.data);
if (results.partial_errors !== undefined)
partial_errors.push(...results.partial_errors);
}
return {
data: globalResults,
partial_errors,
status: created ? 201 : 200,
};
};

export const POST = async (request: NextRequest): Promise<NextResponse> => {
const supabase = await createClient();

try {
const body: ApiInputEmbeddingItem[] = await request.json();
if (!Array.isArray(body)) {
return createApiResponse(request, {
error: "Request body must be an array of embedding items.",
status: 400,
});
}

const result = await batchInsertEmbeddingsProcess(supabase, body);

return createApiResponse(request, {
data: result.data,
error: result.error,
details: result.details,
...(result.partial_errors && {
meta: { partial_errors: result.partial_errors },
}),
status: result.status,
created: result.status === 201,
});
} catch (e: unknown) {
return handleRouteError(
request,
e,
`/api/supabase/insert/content-embedding/batch`,
);
}
};

export const OPTIONS = defaultOptionsHandler;
Loading