Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
57eb25a
eng-1837-add-public-status-in-db
maparent Jun 5, 2026
1ac37af
Routes, with minimalist conversion
maparent Jun 1, 2026
b05ff8b
hackish use of schema
maparent Jun 1, 2026
5bf3200
add mira to jsonld
maparent Jun 1, 2026
25701c1
LDO shapes, v1
maparent Jun 3, 2026
c5cd9b8
Correction: Check that the resource is in the right space
maparent Jun 3, 2026
076cd89
AbstractRelationDef
maparent Jun 4, 2026
437e44e
First version of post (untested)
maparent Jun 4, 2026
1b2d0b4
test (failing)
maparent Jun 4, 2026
b1c4eba
wip
maparent Jun 4, 2026
a8f6b2f
wip
maparent Jun 5, 2026
dfa8709
wip
maparent Jun 5, 2026
e9e12bb
wip
maparent Jun 5, 2026
37b49da
replace dc elements by dcterms
maparent Jun 5, 2026
51dd22f
jsdom external
maparent Jun 5, 2026
c4c3e7f
older jsdom
maparent Jun 5, 2026
9ebac4e
separate mira and dg contexts. Align dct:date->dct:created
maparent Jun 5, 2026
07192dc
extend to mira
maparent Jun 5, 2026
659a2de
another date->created change
maparent Jun 6, 2026
342259e
Add user account as part of upsert logic
maparent Jun 6, 2026
4b1f040
output creator correctly
maparent Jun 6, 2026
a2a5a2b
Creator as object
maparent Jun 6, 2026
5d58254
Expose public content
maparent Jun 6, 2026
25dde27
OpenGraph data
maparent Jun 6, 2026
4e4e73b
ts is really bad at array indices
maparent Jun 6, 2026
c170f10
forgot a log
maparent Jun 6, 2026
a2361db
iriToCurie
maparent Jun 7, 2026
ef94d36
Add extra json attributes to literal_content
maparent Jun 7, 2026
49031a9
check-types
maparent Jun 7, 2026
e5e8b2a
jsonld
maparent Jun 9, 2026
8f33e5d
accountName
maparent Jun 9, 2026
7a0baa8
multiple creators
maparent Jun 9, 2026
b98daf2
indirect description; description containment
maparent Jun 9, 2026
c8160ed
partOf info
maparent Jun 9, 2026
516105c
Repair content reading
maparent Jun 9, 2026
69246c1
update mira jsonld
maparent Jun 10, 2026
285c7a8
commited by mistake
maparent Jun 10, 2026
11a7d7a
more jsonld adjustments
maparent Jun 11, 2026
d6c5f89
more jsonld adjustments
maparent Jun 11, 2026
87bcbd7
mira:Study correction
maparent Jun 22, 2026
30d8f3a
rebase errors
maparent Jun 22, 2026
e96485f
add test (still failing)
maparent Jun 23, 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
Loading