Skip to content

Commit

Permalink
feat(endpoint-posts): split post management into new plug-in
Browse files Browse the repository at this point in the history
  • Loading branch information
paulrobertlloyd committed Jun 14, 2022
1 parent b63ef8d commit cb1bf6c
Show file tree
Hide file tree
Showing 18 changed files with 449 additions and 0 deletions.
26 changes: 26 additions & 0 deletions packages/endpoint-posts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# @indiekit/endpoint-posts

Post management endpoint for Indiekit.

## Installation

`npm i @indiekit/endpoint-posts`

## Usage

Add `@indiekit/endpoint-posts` to your list of plug-ins, specifying options as required:

```jsonc
{
"plugins": ["@indiekit/endpoint-posts"],
"@indiekit/endpoint-posts": {
"mountPath": "/artikel" // de-DE
}
}
```

## Options

| Option | Type | Description |
| :---------- | :------- | :-------------------------------------------------------------- |
| `mountPath` | `string` | Path to management interface. _Optional_, defaults to `/posts`. |
42 changes: 42 additions & 0 deletions packages/endpoint-posts/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import express from "express";
import { postController } from "./lib/controllers/post.js";
import { postsController } from "./lib/controllers/posts.js";

const defaults = {
mountPath: "/posts",
};

export const PostsEndpoint = class {
constructor(options = {}) {
this.id = "endpoint-posts";
this.meta = import.meta;
this.name = "Post management endpoint";
this.options = { ...defaults, ...options };
this.mountPath = this.options.mountPath;
this._router = express.Router(); // eslint-disable-line new-cap
}

navigationItems(application) {
if (application.hasDatabase) {
return {
href: this.options.mountPath,
text: "posts.title",
};
}
}

get routes() {
const router = this._router;

router.get("/", postsController);
router.get("/:id", postController);

return router;
}

init() {
return true;
}
};

export default PostsEndpoint;
53 changes: 53 additions & 0 deletions packages/endpoint-posts/lib/controllers/post.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { mf2tojf2 } from "@paulrobertlloyd/mf2tojf2";
import httpError from "http-errors";

/**
* View previously published post
*
* @param {object} request HTTP request
* @param {object} response HTTP response
* @param {Function} next Next middleware callback
* @returns {object} HTTP response
*/
export const postController = async (request, response, next) => {
try {
const { publication } = request.app.locals;
const { id } = request.params;
const url = Buffer.from(id, "base64").toString("utf8");

const parameters = new URLSearchParams({
q: "source",
url,
}).toString();

const endpointResponse = await fetch(
`${publication.micropubEndpoint}?${parameters}`,
{
headers: {
accept: "application/json",
// TODO: Third-party media endpoint may require a separate token
authorization: `Bearer ${request.session.token}`,
},
}
);

const body = await endpointResponse.json();

if (!endpointResponse.ok) {
throw httpError(
endpointResponse.status,
body.error_description || endpointResponse.statusText
);
}

const post = mf2tojf2(body);

response.render("post", {
title: post.name,
post,
parent: response.__("posts.posts.title"),
});
} catch (error) {
next(error);
}
};
72 changes: 72 additions & 0 deletions packages/endpoint-posts/lib/controllers/posts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Buffer } from "node:buffer";
import { mf2tojf2 } from "@paulrobertlloyd/mf2tojf2";
import httpError from "http-errors";
import { fetch } from "undici";

/**
* List previously published posts
*
* @param {object} request HTTP request
* @param {object} response HTTP response
* @param {Function} next Next middleware callback
* @returns {object} HTTP response
*/
export const postsController = async (request, response, next) => {
try {
const { publication } = request.app.locals;

let { page, limit, offset, success } = request.query;
page = Number.parseInt(page, 10) || 1;
limit = Number.parseInt(limit, 10) || 12;
offset = Number.parseInt(offset, 10) || (page - 1) * limit;

const parameters = new URLSearchParams({
q: "source",
page,
limit,
offset,
}).toString();

const endpointResponse = await fetch(
`${publication.micropubEndpoint}?${parameters}`,
{
headers: {
accept: "application/json",
// TODO: Third-party media endpoint may require a separate token
authorization: `Bearer ${request.session.token}`,
},
}
);

const body = await endpointResponse.json();

if (!endpointResponse.ok) {
throw httpError(
endpointResponse.status,
body.error_description || endpointResponse.statusText
);
}

let posts;
if (body?.items?.length > 0) {
const mf2 = mf2tojf2(body);
const items = mf2.children ? mf2.children : [mf2];
posts = items.map((item) => {
item.id = Buffer.from(item.url).toString("base64");
return item;
});
}

response.render("posts", {
title: response.__("posts.posts.title"),
posts,
page,
limit,
count: await publication.media.countDocuments(),
parentUrl: request.baseUrl + request.path,
success,
});
} catch (error) {
next(error);
}
};
12 changes: 12 additions & 0 deletions packages/endpoint-posts/locales/de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"posts": {
"post": {
"properties": "Eigenschaften"
},
"posts": {
"none": "Keine Beiträge",
"title": "Veröffentlichte Beiträge"
},
"title": "Beiträge"
}
}
12 changes: 12 additions & 0 deletions packages/endpoint-posts/locales/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"posts": {
"post": {
"properties": "Properties"
},
"posts": {
"title": "Published posts",
"none": "No posts"
},
"title": "Posts"
}
}
12 changes: 12 additions & 0 deletions packages/endpoint-posts/locales/es.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"posts": {
"post": {
"properties": "Propiedades"
},
"posts": {
"none": "No hay publicaciones",
"title": "Publicaciones publicadas"
},
"title": "Publicaciones"
}
}
12 changes: 12 additions & 0 deletions packages/endpoint-posts/locales/fr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"posts": {
"post": {
"properties": "Propriétés"
},
"posts": {
"none": "Pas d'articles",
"title": "Articles publiés"
},
"title": "Articles"
}
}
12 changes: 12 additions & 0 deletions packages/endpoint-posts/locales/id.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"posts": {
"post": {
"properties": "Properti"
},
"posts": {
"none": "Tidak ada postingan",
"title": "Postingan yang diterbitkan"
},
"title": "Postingan"
}
}
12 changes: 12 additions & 0 deletions packages/endpoint-posts/locales/nl.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"posts": {
"post": {
"properties": "Eigenschappen"
},
"posts": {
"none": "Geen posts",
"title": "Gepubliceerde berichten"
},
"title": "Berichten"
}
}
12 changes: 12 additions & 0 deletions packages/endpoint-posts/locales/pt.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"posts": {
"post": {
"properties": "Propriedades"
},
"posts": {
"none": "Nenhuma postagem",
"title": "Postagens publicadas"
},
"title": "Postagens"
}
}
44 changes: 44 additions & 0 deletions packages/endpoint-posts/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "@indiekit/endpoint-posts",
"version": "1.0.0-alpha.5",
"description": "Post management endpoint for Indiekit",
"keywords": [
"indiekit",
"indiekit-plugin",
"indieweb"
],
"homepage": "https://getindiekit.com",
"author": {
"name": "Paul Robert Lloyd",
"url": "https://paulrobertlloyd.com"
},
"license": "MIT",
"engines": {
"node": ">=18"
},
"type": "module",
"main": "index.js",
"files": [
"lib",
"locales",
"views",
"index.js"
],
"bugs": {
"url": "https://github.com/getindiekit/indiekit/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/getindiekit/indiekit.git",
"directory": "packages/endpoint-posts"
},
"dependencies": {
"@paulrobertlloyd/mf2tojf2": "^1.0.0",
"express": "^4.17.1",
"http-errors": "^2.0.0",
"undici": "^5.2.0"
},
"publishConfig": {
"access": "public"
}
}
38 changes: 38 additions & 0 deletions packages/endpoint-posts/tests/integration/200-post.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import process from "node:process";
import test from "ava";
import nock from "nock";
import { JSDOM } from "jsdom";
import { testServer } from "@indiekit-test/server";

test("Views previously published post", async (t) => {
nock("https://api.github.com")
.put((uri) => uri.includes("foobar.md"))
.reply(200);

// Create post
const request = await testServer();
await request
.post("/micropub")
.auth(process.env.TEST_TOKEN, { type: "bearer" })
.set("accept", "application/json")
.send("h=entry")
.send("name=Foobar");

// Get post data by parsing list of posts and getting values from link
const postsResponse = await request.get("/posts");
const postsDom = new JSDOM(postsResponse.text);
const postLink = postsDom.window.document.querySelector(".file-list a");
const postName = postLink.textContent;
const postId = postLink.href.split("/").pop();

// Visit post page
const postResponse = await request.get(`/posts/${postId}`);
const postDom = new JSDOM(postResponse.text);

const result = postDom.window.document;

t.is(
result.querySelector("title").textContent,
`${postName} - Test configuration`
);
});
17 changes: 17 additions & 0 deletions packages/endpoint-posts/tests/integration/200-posts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import test from "ava";
import { JSDOM } from "jsdom";
import { testServer } from "@indiekit-test/server";
import { cookie } from "@indiekit-test/session";

test("Returns list of previously published posts", async (t) => {
const request = await testServer();
const response = await request.get("/posts").set("cookie", [cookie]);
const dom = new JSDOM(response.text);

const result = dom.window.document;

t.is(
result.querySelector("title").textContent,
"Published posts - Test configuration"
);
});
Loading

0 comments on commit cb1bf6c

Please sign in to comment.