Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
ece1239
eng-1837-add-public-status-in-db
maparent Jun 5, 2026
f49e0b6
Routes, with minimalist conversion
maparent Jun 1, 2026
dd874c4
hackish use of schema
maparent Jun 1, 2026
b2f6a98
add mira to jsonld
maparent Jun 1, 2026
3bafdb0
LDO tooling
maparent Jun 3, 2026
516869f
LDO shapes, v1
maparent Jun 3, 2026
8ab50d5
Correction: Check that the resource is in the right space
maparent Jun 3, 2026
317aad2
AbstractRelationDef
maparent Jun 4, 2026
6a3b017
First version of post (untested)
maparent Jun 4, 2026
bbbd729
test (failing)
maparent Jun 4, 2026
79d85f1
wip
maparent Jun 4, 2026
7b6d7fd
wip
maparent Jun 5, 2026
fefc362
wip
maparent Jun 5, 2026
b288ac1
wip
maparent Jun 5, 2026
e19d6cf
replace dc elements by dcterms
maparent Jun 5, 2026
7f9514c
jsdom external
maparent Jun 5, 2026
62a4449
older jsdom
maparent Jun 5, 2026
8874a94
separate mira and dg contexts. Align dct:date->dct:created
maparent Jun 5, 2026
769fbed
Rename ClaimOrEvidence to Argument, and define as marker class for re…
maparent Jun 5, 2026
41bb9be
extend to mira
maparent Jun 5, 2026
99df652
another date->created change
maparent Jun 6, 2026
613b46e
Add user account as part of upsert logic
maparent Jun 6, 2026
a7133af
output creator correctly
maparent Jun 6, 2026
2a9684f
Creator as object
maparent Jun 6, 2026
dd7de08
Expose public content
maparent Jun 6, 2026
f34a91a
OpenGraph data
maparent Jun 6, 2026
75a3901
ts is really bad at array indices
maparent Jun 6, 2026
34170f5
forgot a log
maparent Jun 6, 2026
1544db0
iriToCurie
maparent Jun 7, 2026
16b5c7c
Add extra json attributes to literal_content
maparent Jun 7, 2026
ceac739
check-types
maparent Jun 7, 2026
89f24a2
jsonld
maparent Jun 9, 2026
91a6eee
accountName
maparent Jun 9, 2026
0f3e37f
multiple creators
maparent Jun 9, 2026
f85503d
indirect description; description containment
maparent Jun 9, 2026
7a9d00a
partOf info
maparent Jun 9, 2026
2cb6800
Repair content reading
maparent Jun 9, 2026
1718ec9
update mira jsonld
maparent Jun 10, 2026
a9fa48d
commited by mistake
maparent Jun 10, 2026
de4f8c7
more jsonld adjustments
maparent Jun 11, 2026
02b828e
more jsonld adjustments
maparent Jun 11, 2026
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
198 changes: 198 additions & 0 deletions apps/website/app/api/content/[space_id]/[resource_id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import { NextResponse, NextRequest } from "next/server";
import { createClient } from "~/utils/supabase/server";
import { asPostgrestFailure } from "@repo/database/lib/contextFunctions";
import {
defaultOptionsHandler,
createApiResponse,
} from "~/utils/supabase/apiUtils";
import { asJsonLD, conceptName } from "~/utils/conversion/jsonld";
import { Tables, Enums } from "@repo/database/dbTypes";
import { convert, MIMETYPES, type DocType } from "~/utils/conversion/convert";

type Concept = Tables<"Concept">;
type Content = Tables<"Content">;
type PlatformAccount = Tables<"PlatformAccount">;
type Platform = Enums<"Platform">;

export type SegmentDataType = { params: Promise<Record<string, string>> };

export const GET = async (
request: NextRequest,
segmentData: SegmentDataType,
): Promise<NextResponse> => {
const { space_id, resource_id } = await segmentData.params;
let targetFormat: DocType = (request.nextUrl.searchParams.get("format") ??
"html") as DocType;
if (MIMETYPES[targetFormat] === undefined) {
targetFormat = "html";
}
const targetMimetype = MIMETYPES[targetFormat];
if (!targetMimetype) {
return createApiResponse(
request,
asPostgrestFailure("Unsupported format", "404", 404),
);
}
const includeData =
targetFormat === "html" &&
request.nextUrl.searchParams.get("data") !== "false";
const spaceIdN = Number.parseInt(space_id || "NaN");
if (isNaN(spaceIdN)) {
return createApiResponse(
request,
asPostgrestFailure(`${space_id} is not a number`, "type"),
);
}
const resourceIdN = Number.parseInt(resource_id || "NaN");
if (isNaN(resourceIdN)) {
return createApiResponse(
request,
asPostgrestFailure(`${resource_id} is not a number`, "type"),
);
}
const supabase = await createClient();
const spaceResponse = await supabase
.from("Space")
.select()
.eq("id", spaceIdN)
.maybeSingle();
if (spaceResponse.error) {
return createApiResponse(request, spaceResponse);
}
let platform: Platform = "Obsidian";
if (spaceResponse.data) {
platform = spaceResponse.data.platform;
} else {
// consideration: We may not see it because we don't have access,
// We should find a way to check its platform otherwise.
// Let's just keep the Obsidian guess for MIRA demo.
// return createApiResponse(
// request,
// asPostgrestFailure("Space not found", "401", 401),
// );
}
const conceptResponse = await supabase
.from("Concept")
.select()
.eq("id", resourceIdN)
.eq("space_id", spaceIdN)
.maybeSingle();
if (conceptResponse.error) {
return createApiResponse(request, conceptResponse);
}
const concept = conceptResponse.data;
if (!concept) {
return createApiResponse(
request,
asPostgrestFailure("Resource not found", "401", 401),
);
}
const contentResponse = await supabase
.from("Content")
.select()
.eq("source_local_id", concept.source_local_id!);
if (contentResponse.error) {
return createApiResponse(request, conceptResponse);
}
const contents: Content[] = contentResponse.data;
const requestUrlParts = request.url.split("/");
const baseUrl = requestUrlParts
.slice(0, requestUrlParts.length - 1)
.join("/");
const fullContentsArray = contents.filter((c) => c.variant === "full");
const fullContents = fullContentsArray.length
? fullContentsArray[0]
: undefined;
const titleArray = contents.filter((c) => c.variant === "direct");
const title = titleArray.length ? titleArray[0] : undefined;

if (!fullContents) {
return createApiResponse(
request,
asPostgrestFailure("Resource not found", "401", 401),
);
}

// await initRT(rootUrl);
const source: DocType | undefined =
platform === "Obsidian"
? "obsidian"
: platform === "Roam"
? "roam"
: undefined;
let text =
source && source !== targetFormat
? convert(fullContents.text, source, targetFormat)
: fullContents.text;
const isSchema = concept.is_schema;
let schema: Concept | undefined = undefined;
if (!isSchema && concept.schema_id) {
const schemaResponse = await supabase
.from("Concept")
.select()
.eq("id", concept.schema_id)
.maybeSingle();
if (schemaResponse.error) {
return createApiResponse(request, schemaResponse);
}
if (!schemaResponse.data) {
return createApiResponse(
request,
asPostgrestFailure("Resource schema not found", "401", 401),
);
}
schema = schemaResponse.data;
}
const schemaName = conceptName(concept, schema);

if (includeData) {
const authorId = concept.author_id ?? (contents ?? [{}])[0]?.author_id;
let author: PlatformAccount | undefined = undefined;
if (authorId) {
const authorResponse = await supabase
.from("PlatformAccount")
.select()
.eq("id", authorId)
.maybeSingle();
if (authorResponse.data) author = authorResponse.data;
}

const jsonLdData = asJsonLD({
platform,
concept,
baseUrl,
title,
schema,
content: undefined,
author,
targetFormat,
wrap: true,
});
text = `<div id="content">\n<script type="application/ld+json">${JSON.stringify(jsonLdData)}</script>\n${text}\n</div>`;
}
const divideFM = fullContents.text.split("---\n");
const firstTextFragment =
divideFM.length > 2 && divideFM[0]!.trim().length == 0
? divideFM[2]!
: fullContents.text;

const wrap = `<html><head>
${title ? '<meta property="og:title" content="' + title.text + '" />' : ""}
<meta property="og:type" content="${schemaName}" />
<meta property="og:url" content="${request.url}" />
<meta property="og:description" content="${firstTextFragment.substring(0, 80)}" />
</head>
<body>
<main>
${text}
</main>
</body>
</html>
`;

return new NextResponse(wrap, {
headers: { "Content-Type": targetMimetype },
});
};

export const OPTIONS = defaultOptionsHandler;
Loading