@commonpub/layer 0.15.4 → 0.15.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commonpub/layer",
3
- "version": "0.15.4",
3
+ "version": "0.15.6",
4
4
  "type": "module",
5
5
  "main": "./nuxt.config.ts",
6
6
  "files": [
@@ -53,12 +53,12 @@
53
53
  "vue": "^3.4.0",
54
54
  "vue-router": "^4.3.0",
55
55
  "zod": "^4.3.6",
56
- "@commonpub/docs": "0.6.2",
57
- "@commonpub/config": "0.10.0",
58
56
  "@commonpub/auth": "0.5.1",
59
- "@commonpub/protocol": "0.9.9",
60
- "@commonpub/learning": "0.5.0",
61
57
  "@commonpub/editor": "0.7.9",
58
+ "@commonpub/config": "0.10.0",
59
+ "@commonpub/docs": "0.6.2",
60
+ "@commonpub/learning": "0.5.0",
61
+ "@commonpub/protocol": "0.9.9",
62
62
  "@commonpub/ui": "0.8.5"
63
63
  },
64
64
  "devDependencies": {
@@ -4,9 +4,13 @@ import { hubPosts, users } from '@commonpub/schema';
4
4
  import { eq } from 'drizzle-orm';
5
5
 
6
6
  /**
7
- * Hub post AP Note endpoint.
8
- * Serves the Note JSON-LD when requested with AP Accept header.
9
- * Remote instances dereference this URI when processing Announce activities.
7
+ * Middleware: serve ActivityPub Note JSON-LD for hub post URIs.
8
+ *
9
+ * Matches /hubs/{slug}/posts/{postId} with AP Accept headers.
10
+ * Non-AP requests pass through to the Nuxt page renderer.
11
+ *
12
+ * This MUST be a middleware (not a server route) because a server route
13
+ * returning undefined sends HTTP 204, which prevents the Nuxt page from rendering.
10
14
  */
11
15
  export default defineEventHandler(async (event) => {
12
16
  const accept = getRequestHeader(event, 'accept') ?? '';
@@ -16,13 +20,18 @@ export default defineEventHandler(async (event) => {
16
20
 
17
21
  if (!isAPRequest) return;
18
22
 
23
+ const path = getRequestURL(event).pathname;
24
+ // postId must be a UUID — anything else short-circuits before the drizzle
25
+ // query, which would otherwise throw on invalid UUID input and 500 for
26
+ // federation peers that probe bad URIs.
27
+ const match = path.match(/^\/hubs\/([a-z0-9][a-z0-9_-]*)\/posts\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/);
28
+ if (!match) return;
29
+
19
30
  const config = useConfig();
20
31
  if (!config.features.federation || !config.features.federateHubs) return;
21
32
 
22
- const slug = getRouterParam(event, 'slug');
23
- const postId = getRouterParam(event, 'postId');
24
- if (!slug || !postId) return;
25
-
33
+ const slug = match[1]!;
34
+ const postId = match[2]!;
26
35
  const db = useDB();
27
36
  const domain = config.instance.domain;
28
37
 
@@ -51,7 +60,6 @@ export default defineEventHandler(async (event) => {
51
60
 
52
61
  setResponseHeader(event, 'content-type', 'application/activity+json');
53
62
 
54
- // Build Note content — share posts need special handling
55
63
  let noteContent = escapeHtmlForAP(post.content);
56
64
  const ext: Record<string, unknown> = {};
57
65
 
@@ -3,12 +3,15 @@ import { contentItems, users } from '@commonpub/schema';
3
3
  import { eq, and, isNull } from 'drizzle-orm';
4
4
 
5
5
  /**
6
- * Content AP Article endpoint.
7
- * Serves Article JSON-LD when requested with AP Accept header.
8
- * Remote instances dereference this URI when processing:
9
- * - Create activities (content federation)
10
- * - Announce activities (hub share federation)
11
- * - Like/Boost activities targeting content
6
+ * Legacy content URI: /content/:slug
7
+ *
8
+ * AP clients (Accept: application/activity+json) get Article JSON-LD —
9
+ * remote instances still dereference this legacy URI when processing old
10
+ * Create/Announce/Like activities created before the URL restructure.
11
+ *
12
+ * Browsers get a 301 redirect to the canonical /u/:author/:type/:slug URL.
13
+ * Anything else would be a dead 204 (this is a server route, not middleware,
14
+ * and there's no Nuxt page at /content/:slug to fall through to).
12
15
  */
13
16
  export default defineEventHandler(async (event) => {
14
17
  const accept = getRequestHeader(event, 'accept') ?? '';
@@ -16,14 +19,12 @@ export default defineEventHandler(async (event) => {
16
19
  accept.includes('application/activity+json') ||
17
20
  accept.includes('application/ld+json');
18
21
 
19
- if (!isAPRequest) return;
20
-
21
- const config = useConfig();
22
- if (!config.features.federation) return;
23
-
24
22
  const slug = getRouterParam(event, 'slug');
25
- if (!slug) return;
23
+ if (!slug) {
24
+ throw createError({ statusCode: 404, statusMessage: 'Not Found' });
25
+ }
26
26
 
27
+ const config = useConfig();
27
28
  const db = useDB();
28
29
  const domain = config.instance.domain;
29
30
 
@@ -44,12 +45,23 @@ export default defineEventHandler(async (event) => {
44
45
  ))
45
46
  .limit(1);
46
47
 
47
- if (!row) return;
48
+ if (!row) {
49
+ throw createError({ statusCode: 404, statusMessage: 'Content not found' });
50
+ }
51
+
52
+ // Browser: redirect to canonical URL.
53
+ if (!isAPRequest) {
54
+ return sendRedirect(event, `/u/${row.author.username}/${row.content.type}/${row.content.slug}`, 301);
55
+ }
56
+
57
+ // AP: serve Article JSON-LD.
58
+ if (!config.features.federation) {
59
+ throw createError({ statusCode: 404, statusMessage: 'Federation disabled' });
60
+ }
48
61
 
49
62
  setResponseHeader(event, 'content-type', 'application/activity+json');
50
63
 
51
- // Render the content as an AP Article with all CommonPub extensions
52
- const article = contentToArticle(
64
+ return contentToArticle(
53
65
  {
54
66
  id: row.content.id,
55
67
  type: row.content.type,
@@ -66,6 +78,4 @@ export default defineEventHandler(async (event) => {
66
78
  { username: row.author.username, displayName: row.author.displayName ?? row.author.username },
67
79
  domain,
68
80
  );
69
-
70
- return article;
71
81
  });