Skip to content
Draft
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
66 changes: 66 additions & 0 deletions apps/website/app/api/internal/space/[id]/content/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { NextResponse, NextRequest } from "next/server";

import { createClient } from "~/utils/supabase/server";
import {
createApiResponse,
handleRouteError,
defaultOptionsHandler,
} from "~/utils/supabase/apiUtils";
import { asPostgrestFailure } from "@repo/database/lib/contextFunctions";
import type { Json } from "@repo/database/dbTypes";
import { StandaloneCrossAppContent } from "@repo/database/crossAppContracts";
import { crossAppStandaloneContentToDbContent } from "@repo/database/lib/crossAppConverters";
import { getAccountId } from "~/utils/supabase/account";

type ApiParams = Promise<{ id: string }>;
export type SegmentDataType = { params: ApiParams };

export const POST = async (
request: NextRequest,
segmentData: SegmentDataType,
): Promise<NextResponse> => {
const { id: spaceIdS } = await segmentData.params;
const spaceId = Number.parseInt(spaceIdS);
if (Number.isNaN(spaceId))
return createApiResponse(
request,
asPostgrestFailure("Cannot parse space id", "invalid", 403),
);

const supabase = await createClient();

const userId = await getAccountId(supabase);
if (userId === undefined)
return createApiResponse(
request,
asPostgrestFailure("Please login", "invalid", 401),
);
try {
const body = (await request.json()) as StandaloneCrossAppContent[];
// TODO: Zed validator
const content = body
.map((c) =>
crossAppStandaloneContentToDbContent(
{
...c,
createdAt: new Date(c.createdAt),
modifiedAt: new Date(c.modifiedAt || c.createdAt),
},
{ dbId: spaceId },
),
)
.filter((c) => c !== undefined);
Comment thread
maparent marked this conversation as resolved.
if (content.length === 0) throw new Error("Could not translate content");
const result = await supabase.rpc("upsert_content", {
data: content as Json,
v_space_id: spaceId,
v_creator_id: userId,
content_as_document: true,
});
return createApiResponse(request, result);
} catch (e: unknown) {
return handleRouteError(request, e, "/api/supabase/space/[id]/content");
}
};

export const OPTIONS = defaultOptionsHandler;
18 changes: 18 additions & 0 deletions apps/website/app/utils/supabase/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,24 @@ import type { DGSupabaseClient } from "@repo/database/lib/client";

type AgentType = Database["public"]["Enums"]["AgentType"] | "group";

export const getAccountId = async (
client: DGSupabaseClient,
): Promise<number | undefined> => {
const { data, error } = await client.auth.getUser();
if (error || !data?.user) return undefined;
const userData = data.user;
if (typeof userData.id !== "string") return undefined;
const id = userData.id;
const accountReq = await client
.from("PlatformAccount")
.select("id")
.eq("dg_account", id)
.eq("agent_type", "person")
.maybeSingle();
if (accountReq.error) throw accountReq.error;
return accountReq.data?.id;
Comment thread
maparent marked this conversation as resolved.
};

export const getSessionBaseUserData = async (
client: DGSupabaseClient,
): Promise<{
Expand Down
5 changes: 5 additions & 0 deletions packages/database/src/crossAppContracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ type InlineCrossAppTypedContent = InlineCrossAppContent & {
contentType: ContentType;
};

export type StandaloneCrossAppContent = InlineCrossAppTypedContent &
CrossAppBase & {
variant: Enums<"ContentVariant">;
};

// A node instance
export type CrossAppNode = CrossAppBase & {
nodeType: Ref;
Expand Down
80 changes: 80 additions & 0 deletions packages/database/src/lib/crossAppConverters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import {
Ref,
CrossAppEmbedding,
InlineCrossAppContent,
StandaloneCrossAppContent,
CrossAppBase,
CrossAppNode,
} from "../crossAppContracts";
import { LocalContentDataInput, LocalConceptDataInput } from "../inputTypes";
import { Enums, CompositeTypes } from "../dbTypes";

type ContentDataInput = CompositeTypes<"content_local_input">;
type InlineEmbeddingInput = CompositeTypes<"inline_embedding_input">;
type InlineAbstractBase = Partial<CrossAppBase>;

Expand Down Expand Up @@ -39,6 +41,47 @@ const decodeRef = <DbVarName extends string, LocalVarName extends string>(
return decodeLocalRef(ref, localVarName);
};

const decodeRefWithNulls = <
DbVarName extends string,
LocalVarName extends string,
>(
ref: Ref | undefined,
dbVarName: DbVarName,
localVarName: LocalVarName,
): Record<DbVarName, number | null> & Record<LocalVarName, string | null> => {
return {
[dbVarName]: ref && "dbId" in ref ? ref.dbId : null,
[localVarName]: ref && "localId" in ref ? ref.localId : null,
} as Record<DbVarName, number | null> & Record<LocalVarName, string | null>;
};

const decodeRefWithInlineNulls = <
DbVarName extends string,
LocalVarName extends string,
InlineVarName extends string,
>({
ref,
dbVarName,
localVarName,
inlineVarName,
}: {
ref?: Ref;
dbVarName: DbVarName;
localVarName: LocalVarName;
inlineVarName: InlineVarName;
}): Record<DbVarName, number | null> &
Record<LocalVarName, string | null> &
Record<InlineVarName, null> => {
return {
[dbVarName]: null,
[localVarName]: null,
[inlineVarName]: null,
...decodeRef(ref, dbVarName, localVarName),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Runtime error when ref is undefined. The function decodeRefWithInlineNulls accepts an optional ref?: Ref parameter (line 68), but unconditionally calls decodeRef(ref, ...) on line 79. This will crash when ref is undefined, which occurs in the actual usage on lines 144-148 and 149-153 where no ref parameter is passed.

Fix by adding a conditional check:

return {
  [dbVarName]: null,
  [localVarName]: null,
  [inlineVarName]: null,
  ...(ref ? decodeRef(ref, dbVarName, localVarName) : {}),
} as Record<DbVarName, number | null> &
  Record<LocalVarName, string | null> &
  Record<InlineVarName, null>;
Suggested change
...decodeRef(ref, dbVarName, localVarName),
...(ref ? decodeRef(ref, dbVarName, localVarName) : {}),

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

} as Record<DbVarName, number | null> &
Record<LocalVarName, string | null> &
Record<InlineVarName, null>;
};

const crossAppEmbeddingToDbEmbedding = (
embedding: CrossAppEmbedding | undefined,
): InlineEmbeddingInput | undefined =>
Expand Down Expand Up @@ -76,6 +119,43 @@ const inlineCrossAppContentToDbContent = (
});
};

export const crossAppStandaloneContentToDbContent = (
content: StandaloneCrossAppContent | undefined,
space: Ref,
): ContentDataInput | undefined => {
if (content === undefined) return undefined;
return {
source_local_id: content.localId,
text: content.value,
scale: content.scale || "document",
content_type: content.contentType || "text/plain",
variant: content.variant,
created: content.createdAt.toISOString(),
last_modified: (content.modifiedAt || content.createdAt).toISOString(),
Comment thread
maparent marked this conversation as resolved.
embedding_inline: crossAppEmbeddingToDbEmbedding(content.embedding) || null,
...decodeRefWithInlineNulls({
ref: content.author,
dbVarName: "author_id",
localVarName: "author_local_id",
inlineVarName: "author_inline",
}),
...decodeRefWithNulls(space, "space_id", "space_url"),
// provide other explicit null values for type completion
...decodeRefWithInlineNulls({
dbVarName: "creator_id",
localVarName: "creator_local_id",
inlineVarName: "creator_inline",
}),
...decodeRefWithInlineNulls({
dbVarName: "document_id",
localVarName: "document_local_id",
inlineVarName: "document_inline",
}),
...decodeRefWithNulls(undefined, "part_of_id", "part_of_local_id"),
metadata: null,
};
};

export const crossAppNodeToDbContent = (
node: CrossAppNode | undefined,
variant: "full" | "direct",
Expand Down