Skip to content

Commit

Permalink
Merge pull request #1 from benjifs/fix-config
Browse files Browse the repository at this point in the history
[micropub.rocks](https://micropub.rocks)

### Creating Posts (Form-Encoded)
✅ | 100 | Create an h-entry post (form-encoded)
✅ | 101 | Create an h-entry post with multiple categories (form-encoded)
✅ | 104 | Create an h-entry with a photo referenced by URL (form-encoded)
✅ | 107 | Create an h-entry post with one category (form-encoded)

### Creating Posts (JSON)
✅ | 200 | Create an h-entry post (JSON)
✅ | 201 | Create an h-entry post with multiple categories (JSON)
➖ | 202 | Create an h-entry with HTML content (JSON)
✅ | 203 | Create an h-entry with a photo referenced by URL (JSON)
➖ | 204 | Create an h-entry post with a nested object (JSON)
✅ | 205 | Create an h-entry post with a photo with alt text (JSON)
✅ | 206 | Create an h-entry with multiple photos referenced by URL (JSON)

### Creating Posts (Multipart)
✅ | 300 | Create an h-entry with a photo (multipart)
✅ | 301 | Create an h-entry with two photos (multipart)

### Updates
✅ | 400 | Replace a property
✅ | 401 | Add a value to an existing property
✅ | 402 | Add a value to a non-existent property
✅ | 403 | Remove a value from a property
✅ | 404 | Remove a property
✅ | 405 | Reject the request if operation is not an array

### Deletes
✅ | 500 | Delete a post (form-encoded)
✅ | 501 | Delete a post (JSON)
➖ | 502 | Undelete a post (form-encoded)
➖ | 503 | Undelete a post (JSON)

### Query
✅ | 600 | Configuration Query
✅ | 601 | Syndication Endpoint Query
✅ | 602 | Source Query (All Properties)
✅ | 603 | Source Query (Specific Properties)

### Media Endpoint
✔️ | 700 | Upload a jpg to the Media Endpoint
✔️ | 701 | Upload a png to the Media Endpoint
✔️ | 702 | Upload a gif to the Media Endpoint

### Authentication
✅ | 800 | Accept access token in HTTP header
✅ | 801 | Accept access token in POST body
⚠️ | 802 | Does not store access token property
✅ | 803 | Rejects unauthenticated requests
⚠️ | 804 | Rejects unauthorized access tokens
⚠️ | 805 | Rejects multiple authentication methods

✔️ - 700, 701, 702 work but show as failed because of [Issue #102](aaronpk/micropub.rocks#102)
⚠️ - 802 [Issue #103](aaronpk/micropub.rocks#103)
⚠️ - 804 [Issue #101](aaronpk/micropub.rocks#101)
⚠️ - 805 [Issue #104](aaronpk/micropub.rocks#104)
➖ - Unsupported
  • Loading branch information
benjifs committed Oct 11, 2021
2 parents c29cdc2 + 37311e8 commit 6dba14c
Show file tree
Hide file tree
Showing 8 changed files with 306 additions and 104 deletions.
11 changes: 8 additions & 3 deletions src/libs/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,20 @@ const Auth = {
headers: {
accept: 'application/json',
Authorization: `Bearer ${token}`
}
},
responseType: 'json'
})
return body && JSON.parse(body)
return body
} catch (err) {
console.error(err)
}
},
isAuthorized: async (headers, body, required_scope) => {
console.log('HEADERS:', headers)
console.log('BODY:', body)
if (headers.authorization && headers.authorization.split(' ')[1] && body.access_token) {
return Error.INVALID
}
const token = (headers.authorization && headers.authorization.split(' ')[1]) || body.access_token
if (!token) {
return Error.UNAUTHORIZED
Expand All @@ -29,7 +33,8 @@ const Auth = {
return Error.FORBIDDEN
}
const valid_scopes = auth.scope.split(' ')
if (!valid_scopes.includes(required_scope)) {
// Checks if at least one of the values in `required_scope` is in `valid_scopes`
if (!required_scope.split(' ').some(scope => valid_scopes.includes(scope))) {
return Error.SCOPE
}
}
Expand Down
51 changes: 40 additions & 11 deletions src/libs/content.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,52 @@

import fm from 'front-matter'
import { utils } from './utils'

export default {
const DEFAULT_CONTENT_DIR = 'src'

const content = {
output: data => {
if (!data) {
return null
}
return '---\n' +
`date: ${data.date}\n` +
(data.title ? `title: ${data.title}\n` : '') +
(data.tags ? `tags:\n - ${data.tags.join('\n - ')}\n` : '') +
(data.name ? `title: ${data.name}\n` : '') +
(data.category && data.category.length ? `tags:\n - ${data.category.join('\n - ')}\n` : '') +
(data.deleted ? 'deleted: true\n' : '') +
(data.draft ? 'draft: true\n' : '') +
(data.updated ? `updated: ${data.updated}\n` : '') +
'---\n\n' +
data.content
},
parse: content => {
console.log('parse:', content)
const { attributes, body } = fm(content.toString())

format: data => {
if (!data) {
return null
}
const date = new Date()
if (!data.date) {
data.date = date.toISOString()
} else {
data.updated = date.toISOString()
}
const type = data.name ? 'posts' : 'notes'
let slug
if (data.slug) {
slug = `${type}/${utils.slugify(data.slug)}`
} else {
const ts = Math.round(date / 1000)
slug = `${type}/${ts}` + (data.name ? `-${utils.slugify(data.name)}` : '')
}
const dir = (process.env.CONTENT_DIR || DEFAULT_CONTENT_DIR).replace(/\/$/, '')
const filename = `${dir}/${slug}.md`

return {
date: attributes.date.toISOString(),
title: attributes.title,
tags: attributes.tags,
content: body
'filename': filename,
'slug': slug,
'formatted': content.output(data),
'data': data
}
}
}

export default content
84 changes: 84 additions & 0 deletions src/libs/parse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@

import fm from 'front-matter'

const stringFromProp = prop => {
if (prop) {
return Array.isArray(prop) ? prop[0] : prop
}
}

const itemsToArray = items => {
return !items ? [] : (Array.isArray(items) ? items : [items])
}

export default {
stringFromProp: stringFromProp,
itemsToArray: itemsToArray,

fromJSON: json => {
if (!json || !json.type || !json.properties) {
return null
}
const { type, properties } = json
const category = itemsToArray(properties.category)
return {
'type': stringFromProp(type),
'content': stringFromProp(properties.content),
'name': stringFromProp(properties.name),
'category': category.length ? category : null,
'photo': itemsToArray(properties.photo),
'slug': stringFromProp(properties['mp-slug']),
'status': stringFromProp(properties['post-status']),
'visibility': stringFromProp(properties['visibility'])
}
},

fromForm: form => {
if (!form || !form.h) {
return null
}
const category = itemsToArray(form.category)
return {
'type': form.h ? `h-${form.h}` : null,
'content': form.content,
'name': form.name,
'category': category.length ? category : null,
'photo': [
// photos could come in as either `photo` or `file`
// handle `photo[]` and `file[]` for multiple files for now
...itemsToArray(form.photo), ...itemsToArray(form.file),
...itemsToArray(form['photo[]']), ...itemsToArray(form['file[]'])],
'slug': form['mp-slug'],
'status': form['post-status'],
'visibility': form.visibility
}
},

fromFrontMatter: data => {
const { attributes, body } = fm(data.toString())
return {
'type': attributes.type || 'h-entry',
'content': body,
'name': attributes.title,
'category': attributes.tags,
'date': attributes.date.toISOString(),
'updated': attributes.updated ? attributes.updated.toISOString : null,
'status': attributes.draft ? 'draft' : null,
'deleted': attributes.deleted
}
},

toSource: data => {
return {
'type': data.type,
'properties': {
'name': data.name ? [data.name] : null,
'summary': data.summary ? [data.summary] : null,
'content': [data.content],
'published': [data.date],
'updated': [data.updated],
'category': data.category
}
}
}
}
107 changes: 89 additions & 18 deletions src/libs/publish.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,96 @@

import GitHub from './github'
import content from './content'
import parse from './parse'
import { utils } from './utils'

export default {
addContent: async (data, json) => {
if (data.photo || data.file) {
const upload = await GitHub.uploadImage(data.file || data.photo)
if (upload) {
data.content = `![](/${upload})\n\n${data.content}`
const uploadFiles = async files => {
const photos = []
for (let file of files) {
if (file.filename) {
const uploaded = await GitHub.uploadImage(file)
if (uploaded) {
photos.push({ 'value': uploaded })
}
} else if (file.alt || file.value) {
photos.push(file)
} else {
photos.push({ 'value': file })
}
const formatted = utils.format(json ? utils.parseJSON(data) : data)
console.log('└─>', formatted)
if (!formatted.content) {
}
return photos
}

const handleUpdate = (body, parsed) => {
if (!body && !body.replace && !body.add && !body.delete) {
return
}
if (body.delete && Array.isArray(body.delete)) {
for (let key of body.delete) {
delete parsed[key]
}
return parsed
}
const updates = utils.removeEmpty(parse.fromJSON({
'type': parsed.type,
'properties': body.replace || body.add || body.delete
}))
if (!updates || Object.entries(updates).length <= 1) { // `updates` always has property 'type'
return
}
if (body.replace) {
return { ...parsed, ...updates }
} else {
for (let [key, value] of Object.entries(updates)) {
if (key == 'type' || key == 'photo') { // skip these properties
continue
}
if (body.add) {
parsed[key] = parsed[key] || []
parsed[key] = [ ...parsed[key], ...updates[key] ]
} else if (body.delete && parsed[key] && Array.isArray(parsed[key])) {
// Only deletes here if the original value was an array
// Look for the specific item to delete from a potential list of values
for (let item of value) {
// Remove `item` from `parsed[key]` if it exists
if (parsed[key].includes(item)) {
parsed[key].splice(parsed[key].indexOf(item), 1)
}
}
}
}
return parsed
}
}

export default {
addContent: async (data, isJSON) => {
const parsed = isJSON ? parse.fromJSON(data) : parse.fromForm(data)
console.log('└─>', parsed)
if (!parsed || !parsed.content) {
return { 'error': 'content is empty' }
}
const exists = await GitHub.getFile(formatted.filename)
const uploaded = await uploadFiles(parsed.photo)
if (uploaded && uploaded.length) {
let imageContent = ''
for (let img of uploaded) {
if (img.value) {
imageContent += `![${img.alt || ''}](/${img.value})\n\n`
}
}
parsed.content = `${imageContent}${parsed.content}`
}
const out = content.format(parsed)
if (!out || !out.filename || !out.formatted) {
return { 'error': 'could not parse data' }
}
const exists = await GitHub.getFile(out.filename)
if (exists) {
return { 'error': 'file exists' }
}
const filename = await GitHub.createFile(formatted.filename, content.output(formatted))
const filename = await GitHub.createFile(out.filename, out.formatted)
if (filename) {
return { 'filename': formatted.slug }
return { 'filename': out.slug }
}
},
updateContent: async (url, body) => {
Expand All @@ -34,16 +102,19 @@ export default {
if (!exists) {
return { 'error': 'file does not exist' }
}
const parsed = content.parse(exists.content)
let parsed = parse.fromFrontMatter(exists.content)
if (!parsed) {
return { 'error': 'could not parse file' }
}
if (body.replace && body.replace.content) {
parsed.content = utils.stringFromProp(body.replace.content)
} else {
return { 'error': 'nothing to update' }
const updated = handleUpdate(body, parsed)
if (!updated) {
return { 'error': 'nothing to update' }
}
const out = content.format(updated)
if (!out || !out.filename || !out.formatted) {
return { 'error': 'could not parse data' }
}
const res = await GitHub.updateFile(filename, content.output(parsed), exists)
const res = await GitHub.updateFile(filename, out.formatted, exists)
if (!res) {
return { 'error': 'file cannot be updated'}
}
Expand Down
47 changes: 47 additions & 0 deletions src/libs/source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@

import GitHub from './github'
import content from './content'
import { utils } from './utils'

const parsedToSource = (parsed, properties) => {
const source = {
'type': parsed.type,
'properties': {
'name': parsed.title,
'summary': parsed.summary,
'content': [parsed.content],
'published': [parsed.date],
'updated': [parsed.updated],
'category': parsed.tags
}
}
if (properties) {
delete source.type
for (let key in source.properties) {
if (!properties.includes(key)) {
delete source.properties[key]
}
}
}
return source
}

export default {
get: async (url, properties) => {
const filename = utils.urlToFilename(url)
if (!filename) {
return { 'error': 'invalid url' }
}
const exists = await GitHub.getFile(filename)
if (!exists) {
return { 'error': 'file does not exist' }
}
const parsed = content.parse(exists.content)
if (!parsed) {
return { 'error': 'could not parse file' }
}
return {
'source': parsedToSource(parsed, properties)
}
}
}
Loading

0 comments on commit 6dba14c

Please sign in to comment.