From 475238df778147b4a39245f046440cf1664edc74 Mon Sep 17 00:00:00 2001 From: Benji Encalada Mora Date: Thu, 7 Oct 2021 00:04:59 -0500 Subject: [PATCH 01/16] fix: return INVALID if passing token in header and body --- src/libs/auth.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libs/auth.js b/src/libs/auth.js index b340f1f..2e95236 100644 --- a/src/libs/auth.js +++ b/src/libs/auth.js @@ -20,6 +20,9 @@ const Auth = { 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 From c3e23f55e2480788571d9b7184e283360d0a1ef1 Mon Sep 17 00:00:00 2001 From: Benji Encalada Mora Date: Thu, 7 Oct 2021 02:10:16 -0500 Subject: [PATCH 02/16] fix: minor cleanup --- src/libs/auth.js | 5 +++-- src/libs/content.js | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libs/auth.js b/src/libs/auth.js index 2e95236..72f6be0 100644 --- a/src/libs/auth.js +++ b/src/libs/auth.js @@ -10,9 +10,10 @@ const Auth = { headers: { accept: 'application/json', Authorization: `Bearer ${token}` - } + }, + responseType: 'json' }) - return body && JSON.parse(body) + return body } catch (err) { console.error(err) } diff --git a/src/libs/content.js b/src/libs/content.js index 06c5cfb..f195bdd 100644 --- a/src/libs/content.js +++ b/src/libs/content.js @@ -14,6 +14,7 @@ export default { console.log('parse:', content) const { attributes, body } = fm(content.toString()) return { + type: attributes.type || 'h-entry', date: attributes.date.toISOString(), title: attributes.title, tags: attributes.tags, From d4f335006e6dcc725f573ed7fe11992aaebadfa7 Mon Sep 17 00:00:00 2001 From: Benji Encalada Mora Date: Thu, 7 Oct 2021 02:11:32 -0500 Subject: [PATCH 03/16] feat: handle additional GET queries --- src/libs/source.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ src/micropub.js | 30 ++++++++++++++++++++++++------ 2 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 src/libs/source.js diff --git a/src/libs/source.js b/src/libs/source.js new file mode 100644 index 0000000..814ffaf --- /dev/null +++ b/src/libs/source.js @@ -0,0 +1,45 @@ + +import GitHub from './github' +import content from './content' +import { utils } from './utils' + +const parsedToSource = (parsed, properties) => { + const source = { + 'type': parsed.type, + 'properties': { + 'published': [parsed.date], + 'content': [parsed.content], + 'category': parsed.tags, + 'title': parsed.title + } + } + 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) + } + } +} diff --git a/src/micropub.js b/src/micropub.js index 5adcaa7..312560e 100644 --- a/src/micropub.js +++ b/src/micropub.js @@ -9,17 +9,35 @@ import jsonBodyParser from '@middy/http-json-body-parser' dotenv.config() import auth from './libs/auth' +import source from './libs/source' import publish from './libs/publish' import { Error, Response } from './libs/response' +// q=source&properties%5B%5D=content&properties%5B%5D=category&url=https%3A%2F%2Fbenji.dog%2Fnotes%2F1632514908 +const getHandler = async query => { + let res + if (query.q === 'config') { + return Response.send(200, { + 'media-endpoint': process.env.MEDIA_ENDPOINT || `${process.env.URL}/.netlify/functions/media`, + 'syndicate-to': [] + }) + } else if (query.q === 'source' && query.url) { + res = await source.get(query.url, query.properties || query['properties[]']) + if (res && res.source) { + return Response.send(200, res.source) + } + } else if (query.q === 'syndicate-to') { + return Response.send(200, { + 'syndicate-to': [] + }) + } + return Response.error(Error.INVALID, res && res.error) +} const micropubFn = async event => { + if (event.httpMethod == 'GET') { + return getHandler(event.queryStringParameters) + } if (event.httpMethod !== 'POST') { - const query = event.queryStringParameters - if (query && query.q === 'config') { - return Response.send(200, { - 'media-endpoint': process.env.MEDIA_ENDPOINT || `${process.env.URL}/.netlify/functions/media` - }) - } return Response.error(Error.NOT_ALLOWED) } From 6994720a704e08cc585de71309f97d73ff7e5691 Mon Sep 17 00:00:00 2001 From: Benji Encalada Mora Date: Thu, 7 Oct 2021 02:45:23 -0500 Subject: [PATCH 04/16] fix: check for multiple scopes and allow media to upload on create --- src/libs/auth.js | 2 +- src/media.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/auth.js b/src/libs/auth.js index 72f6be0..ab8524f 100644 --- a/src/libs/auth.js +++ b/src/libs/auth.js @@ -33,7 +33,7 @@ const Auth = { return Error.FORBIDDEN } const valid_scopes = auth.scope.split(' ') - if (!valid_scopes.includes(required_scope)) { + if (!required_scope.split(' ').some(scope => valid_scopes.includes(scope))) { return Error.SCOPE } } diff --git a/src/media.js b/src/media.js index 5d19ce7..3d50c81 100644 --- a/src/media.js +++ b/src/media.js @@ -16,7 +16,7 @@ const mediaFn = async event => { } const { headers, body } = event - const error = await auth.isAuthorized(headers, body, 'media') + const error = await auth.isAuthorized(headers, body, 'media create') if (error) { return Response.error(error) } From cc5e017ae6524110f12e4f0b9ebf3c9e615a32f1 Mon Sep 17 00:00:00 2001 From: Benji Encalada Mora Date: Thu, 7 Oct 2021 02:45:39 -0500 Subject: [PATCH 05/16] fix: cleanup --- src/micropub.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/micropub.js b/src/micropub.js index 312560e..eeb5aea 100644 --- a/src/micropub.js +++ b/src/micropub.js @@ -12,7 +12,7 @@ import auth from './libs/auth' import source from './libs/source' import publish from './libs/publish' import { Error, Response } from './libs/response' -// q=source&properties%5B%5D=content&properties%5B%5D=category&url=https%3A%2F%2Fbenji.dog%2Fnotes%2F1632514908 + const getHandler = async query => { let res if (query.q === 'config') { From 5b3509bfaa1ef249a0ec7986c0ec00987d1b036a Mon Sep 17 00:00:00 2001 From: Benji Encalada Mora Date: Fri, 8 Oct 2021 23:57:53 -0500 Subject: [PATCH 06/16] docs: add comment --- src/libs/auth.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/auth.js b/src/libs/auth.js index ab8524f..3f145ba 100644 --- a/src/libs/auth.js +++ b/src/libs/auth.js @@ -33,6 +33,7 @@ const Auth = { return Error.FORBIDDEN } const valid_scopes = auth.scope.split(' ') + // 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 } From b012fdfb55ca0358f565dec906f6e7551c298679 Mon Sep 17 00:00:00 2001 From: Benji Encalada Mora Date: Sat, 9 Oct 2021 02:08:56 -0500 Subject: [PATCH 07/16] feat: trying to do some cleanup before fixing some issues --- src/libs/content.js | 52 +++++++++++++++++++++++------- src/libs/parse.js | 69 +++++++++++++++++++++++++++++++++++++++ src/libs/publish.js | 36 ++++++++++++++------- src/libs/source.js | 8 +++-- src/libs/utils.js | 78 ++++++++------------------------------------- 5 files changed, 152 insertions(+), 91 deletions(-) create mode 100644 src/libs/parse.js diff --git a/src/libs/content.js b/src/libs/content.js index f195bdd..cb3a4ce 100644 --- a/src/libs/content.js +++ b/src/libs/content.js @@ -1,24 +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 ? `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(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 { - type: attributes.type || 'h-entry', - 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 diff --git a/src/libs/parse.js b/src/libs/parse.js new file mode 100644 index 0000000..0f40c80 --- /dev/null +++ b/src/libs/parse.js @@ -0,0 +1,69 @@ + +import fm from 'front-matter' + +const stringFromProp = prop => { + if (prop) { + return Array.isArray(prop) ? prop[0] : prop + } +} + +export default { + fromJSON: json => { + if (!json || !json.type || !json.properties) { + return null + } + const { type, properties } = json + return { + 'type': stringFromProp(type), + 'content': stringFromProp(properties.content), + 'name': stringFromProp(properties.name), + 'category': properties.category, + 'slug': stringFromProp(properties['mp-slug']), + 'status': stringFromProp(properties['post-status']), + 'visibility': stringFromProp(properties['visibility']) + } + }, + + fromForm: form => { + if (!form || !form.h) { + return null + } + return { + 'type': form.h ? `h-${form.h}` : null, + 'content': form.content, + 'name': form.name, + 'category': form.category, + '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 + } + } + } +} diff --git a/src/libs/publish.js b/src/libs/publish.js index a59dc70..dc748ef 100644 --- a/src/libs/publish.js +++ b/src/libs/publish.js @@ -1,6 +1,7 @@ import GitHub from './github' import content from './content' +import parse from './parse' import { utils } from './utils' export default { @@ -11,18 +12,22 @@ export default { data.content = `![](/${upload})\n\n${data.content}` } } - const formatted = utils.format(json ? utils.parseJSON(data) : data) - console.log('└─>', formatted) - if (!formatted.content) { + const parsed = json ? 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 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) => { @@ -34,16 +39,25 @@ 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 { + if (!body.replace || !body.replace.content) { return { 'error': 'nothing to update' } } - const res = await GitHub.updateFile(filename, content.output(parsed), exists) + const replace = utils.removeEmpty(parse.fromJSON({ + 'type': parsed.type, + 'properties': body.replace + })) + // Merge properties from `replace` into `parsed` + parsed = { ...parsed, ...replace } + + const out = content.format(parsed) + if (!out || !out.filename || !out.formatted) { + return { 'error': 'could not parse data' } + } + const res = await GitHub.updateFile(filename, out.formatted, exists) if (!res) { return { 'error': 'file cannot be updated'} } diff --git a/src/libs/source.js b/src/libs/source.js index 814ffaf..ec02a91 100644 --- a/src/libs/source.js +++ b/src/libs/source.js @@ -7,10 +7,12 @@ const parsedToSource = (parsed, properties) => { const source = { 'type': parsed.type, 'properties': { - 'published': [parsed.date], + 'name': parsed.title, + 'summary': parsed.summary, 'content': [parsed.content], - 'category': parsed.tags, - 'title': parsed.title + 'published': [parsed.date], + 'updated': [parsed.updated], + 'category': parsed.tags } } if (properties) { diff --git a/src/libs/utils.js b/src/libs/utils.js index 7dc8386..51893f7 100644 --- a/src/libs/utils.js +++ b/src/libs/utils.js @@ -1,77 +1,25 @@ -const DEFAULT_CONTENT_DIR = 'src' - -const stringFromProp = prop => { - if (prop) { - return Array.isArray(prop) ? prop[0] : prop - } - return null -} - -const slugify = text => { - return text - .toLowerCase() - .replace(/ /g, '-') - .replace(/[^\w-]+/g, '') -} - const Base64 = { encode: content => Buffer.from(content).toString('base64'), decode: content => Buffer.from(content, 'base64').toString('utf8') } const utils = { - stringFromProp: stringFromProp, - - parseJSON: json => { - if (!json || !json.type || !json.properties) { - return null - } - const parsed = { - 'h': '', - 'content': '', - 'name': '', - 'category': [], - 'mp-slug': '', - 'post-status': 'published', - 'visibility': 'public' - } - const { type, properties } = json - - parsed.h = stringFromProp(type).replace('h-', '') - parsed.content = stringFromProp(properties.content) - parsed.name = stringFromProp(properties.name) - parsed.category = properties.category - parsed['mp-slug'] = stringFromProp(properties['mp-slug']) - - console.log('parseJSON\n└─>', parsed) - return parsed + slugify: text => { + return text + .toLowerCase() + .replace(/[^\w- ]+/g, '') + .trim() + .replace(/ /g, '-') }, - format: data => { - console.log('format:', data) - const dir = (process.env.CONTENT_DIR || DEFAULT_CONTENT_DIR).replace(/\/$/, '') - const date = new Date() - const datetime = date.toISOString() - const timestamp = Math.round(date / 1000) - - let slug = data.name ? 'posts' : 'notes' - if (data['mp-slug']) { - slug += `/${slugify(data['mp-slug'])}` - } else if (data.name) { - slug += `/${timestamp}-${slugify(data.name)}` - } else { - slug += `/${timestamp}` - } - - return { - slug: slug, - filename: `${dir}/${slug}.md`, - date: datetime, - title: data.name, - tags: data.category, - content: data.content + removeEmpty: data => { + for (let i in data) { + if (data[i] === undefined || data[i] === null) { + delete data[i] + } } + return data }, urlToFilename: urlString => { @@ -80,7 +28,7 @@ const utils = { if (url && url.origin == process.env.ME.replace(/\/$/, '') && url.pathname) { - const dir = (process.env.CONTENT_DIR || DEFAULT_CONTENT_DIR).replace(/\/$/, '') + const dir = (process.env.CONTENT_DIR || 'src').replace(/\/$/, '') return `${dir}/${url.pathname.replace(/^\/|\/$/g, '')}.md` } } catch (err) { From 64d07379a19fc73b87f814394b959cbda891053b Mon Sep 17 00:00:00 2001 From: Benji Encalada Mora Date: Sat, 9 Oct 2021 02:16:22 -0500 Subject: [PATCH 08/16] fix: wrong slug --- src/libs/content.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/content.js b/src/libs/content.js index cb3a4ce..44752b2 100644 --- a/src/libs/content.js +++ b/src/libs/content.js @@ -32,7 +32,7 @@ const content = { const type = data.name ? 'posts' : 'notes' let slug if (data.slug) { - slug = `${type}/${utils.slugify(slug)}` + slug = `${type}/${utils.slugify(data.slug)}` } else { const ts = Math.round(date / 1000) slug = `${type}/${ts}` + (data.name ? `-${utils.slugify(data.name)}` : '') From 417ca89eabaddc1f992c2cbb44759d246631baf2 Mon Sep 17 00:00:00 2001 From: Benji Encalada Mora Date: Sat, 9 Oct 2021 02:31:01 -0500 Subject: [PATCH 09/16] fix: for 107 from micropub.rocks --- src/libs/parse.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/parse.js b/src/libs/parse.js index 0f40c80..26d1b47 100644 --- a/src/libs/parse.js +++ b/src/libs/parse.js @@ -32,7 +32,7 @@ export default { 'type': form.h ? `h-${form.h}` : null, 'content': form.content, 'name': form.name, - 'category': form.category, + 'category': Array.isArray(form.category) ? form.category : [form.category], 'slug': form['mp-slug'], 'status': form['post-status'], 'visibility': form.visibility From 774940abd410badfee1100691b03966f1184f985 Mon Sep 17 00:00:00 2001 From: Benji Encalada Mora Date: Sun, 10 Oct 2021 13:41:39 -0500 Subject: [PATCH 10/16] fix: set category to null if no items --- src/libs/parse.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/parse.js b/src/libs/parse.js index 26d1b47..2f1815c 100644 --- a/src/libs/parse.js +++ b/src/libs/parse.js @@ -32,7 +32,7 @@ export default { 'type': form.h ? `h-${form.h}` : null, 'content': form.content, 'name': form.name, - 'category': Array.isArray(form.category) ? form.category : [form.category], + 'category': !form.category ? null : (Array.isArray(form.category) ? form.category : [form.category]), 'slug': form['mp-slug'], 'status': form['post-status'], 'visibility': form.visibility From cd14e32659df56f26fddee7b1f3e06cf37adf332 Mon Sep 17 00:00:00 2001 From: Benji Encalada Mora Date: Sun, 10 Oct 2021 13:42:09 -0500 Subject: [PATCH 11/16] feat: try to handle multiple files, photos referenced by URL, and alt --- src/libs/publish.js | 44 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/libs/publish.js b/src/libs/publish.js index dc748ef..7be8de1 100644 --- a/src/libs/publish.js +++ b/src/libs/publish.js @@ -4,12 +4,48 @@ import content from './content' import parse from './parse' import { utils } from './utils' +// If empty -> return empty array +// If array -> return full array +// else -> return array of single item +const getFile = file => { + return !file ? [] : (Array.isArray(file) ? file : [file]) +} + +const uploadFiles = async data => { + if (!data) { + return [] + } + let files = [], uploaded = [] + files = files.concat(getFile(data.photo)) + files = files.concat(getFile(data.file)) + files = files.concat(getFile(data['photo[]'])) + files = files.concat(getFile(data['file[]'])) + for (let i in files) { + let upload + if (files[i].filename) { + const tmp = await GitHub.uploadImage(files[i]) + if (tmp) { + upload = { 'value': tmp } + } + } else if (files[i].alt || files[i].value) { + upload = files[i] + } else { + upload = { 'value': files[i] } + } + upload && uploaded.push(upload) + } + return uploaded +} + 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 uploaded = await uploadFiles(data) + if (uploaded && uploaded.length) { + for (let i in uploaded) { + const { alt, value } = uploaded[i] + if (value) { + data.content = `![${alt || ''}](/${value})\n\n${data.content}` + } } } const parsed = json ? parse.fromJSON(data) : parse.fromForm(data) From 0711899106fb78863fc8fe796c8292ef647560c3 Mon Sep 17 00:00:00 2001 From: Benji Encalada Mora Date: Sun, 10 Oct 2021 22:38:00 -0500 Subject: [PATCH 12/16] feat: move itemsToArray to parse and use for category and photo --- src/libs/parse.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/libs/parse.js b/src/libs/parse.js index 2f1815c..d724516 100644 --- a/src/libs/parse.js +++ b/src/libs/parse.js @@ -7,7 +7,14 @@ const stringFromProp = 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 @@ -17,7 +24,8 @@ export default { 'type': stringFromProp(type), 'content': stringFromProp(properties.content), 'name': stringFromProp(properties.name), - 'category': properties.category, + 'category': itemsToArray(properties.category), + 'photo': itemsToArray(properties.photo), 'slug': stringFromProp(properties['mp-slug']), 'status': stringFromProp(properties['post-status']), 'visibility': stringFromProp(properties['visibility']) @@ -32,7 +40,12 @@ export default { 'type': form.h ? `h-${form.h}` : null, 'content': form.content, 'name': form.name, - 'category': !form.category ? null : (Array.isArray(form.category) ? form.category : [form.category]), + 'category': itemsToArray(form.category), + '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 From 00cef8f01f070f4706797daedb0b38ddfd094679 Mon Sep 17 00:00:00 2001 From: Benji Encalada Mora Date: Sun, 10 Oct 2021 22:42:22 -0500 Subject: [PATCH 13/16] feat: update publish and content with new parse --- src/libs/content.js | 2 +- src/libs/publish.js | 51 +++++++++++++++++---------------------------- 2 files changed, 20 insertions(+), 33 deletions(-) diff --git a/src/libs/content.js b/src/libs/content.js index 44752b2..e9b25a0 100644 --- a/src/libs/content.js +++ b/src/libs/content.js @@ -11,7 +11,7 @@ const content = { return '---\n' + `date: ${data.date}\n` + (data.name ? `title: ${data.name}\n` : '') + - (data.category ? `tags:\n - ${data.category.join('\n - ')}\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` : '') + diff --git a/src/libs/publish.js b/src/libs/publish.js index 7be8de1..f0f92d1 100644 --- a/src/libs/publish.js +++ b/src/libs/publish.js @@ -4,55 +4,42 @@ import content from './content' import parse from './parse' import { utils } from './utils' -// If empty -> return empty array -// If array -> return full array -// else -> return array of single item -const getFile = file => { - return !file ? [] : (Array.isArray(file) ? file : [file]) -} - -const uploadFiles = async data => { - if (!data) { - return [] - } - let files = [], uploaded = [] - files = files.concat(getFile(data.photo)) - files = files.concat(getFile(data.file)) - files = files.concat(getFile(data['photo[]'])) - files = files.concat(getFile(data['file[]'])) +const uploadFiles = async files => { + const photos = [] for (let i in files) { - let upload if (files[i].filename) { - const tmp = await GitHub.uploadImage(files[i]) - if (tmp) { - upload = { 'value': tmp } + const uploaded = await GitHub.uploadImage(files[i]) + if (uploaded) { + photos.push({ 'value': uploaded }) } } else if (files[i].alt || files[i].value) { - upload = files[i] + photos.push(files[i]) } else { - upload = { 'value': files[i] } + photos.push({ 'value': files[i] }) } - upload && uploaded.push(upload) } - return uploaded + return photos } export default { - addContent: async (data, json) => { - const uploaded = await uploadFiles(data) + 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 uploaded = await uploadFiles(parsed.photo) if (uploaded && uploaded.length) { + let imageContent = '' for (let i in uploaded) { const { alt, value } = uploaded[i] if (value) { - data.content = `![${alt || ''}](/${value})\n\n${data.content}` + imageContent += `![${alt || ''}](/${value})\n\n` } } + parsed.content = `${imageContent}${parsed.content}` } - const parsed = json ? parse.fromJSON(data) : parse.fromForm(data) - console.log('└─>', parsed) - if (!parsed || !parsed.content) { - return { 'error': 'content is empty' } - } + console.log(parsed.content) const out = content.format(parsed) if (!out || !out.filename || !out.formatted) { return { 'error': 'could not parse data' } From 491b6eb1d981aafbf9e00e2f771c519056f153a2 Mon Sep 17 00:00:00 2001 From: Benji Encalada Mora Date: Mon, 11 Oct 2021 00:11:50 -0500 Subject: [PATCH 14/16] fix: category should be null if empty --- src/libs/parse.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libs/parse.js b/src/libs/parse.js index d724516..18e0f90 100644 --- a/src/libs/parse.js +++ b/src/libs/parse.js @@ -20,11 +20,12 @@ export default { return null } const { type, properties } = json + const category = itemsToArray(properties.category) return { 'type': stringFromProp(type), 'content': stringFromProp(properties.content), 'name': stringFromProp(properties.name), - 'category': itemsToArray(properties.category), + 'category': category.length ? category : null, 'photo': itemsToArray(properties.photo), 'slug': stringFromProp(properties['mp-slug']), 'status': stringFromProp(properties['post-status']), @@ -36,11 +37,12 @@ export default { 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': itemsToArray(form.category), + 'category': category.length ? category : null, 'photo': [ // photos could come in as either `photo` or `file` // handle `photo[]` and `file[]` for multiple files for now From fdefa4e7cb79a7eef45bc65e7bc1fff2945df264 Mon Sep 17 00:00:00 2001 From: Benji Encalada Mora Date: Mon, 11 Oct 2021 00:34:40 -0500 Subject: [PATCH 15/16] feat: update --- src/libs/publish.js | 75 ++++++++++++++++++++++++++++++++------------- src/libs/utils.js | 2 +- 2 files changed, 55 insertions(+), 22 deletions(-) diff --git a/src/libs/publish.js b/src/libs/publish.js index f0f92d1..426d405 100644 --- a/src/libs/publish.js +++ b/src/libs/publish.js @@ -6,21 +6,62 @@ import { utils } from './utils' const uploadFiles = async files => { const photos = [] - for (let i in files) { - if (files[i].filename) { - const uploaded = await GitHub.uploadImage(files[i]) + for (let file of files) { + if (file.filename) { + const uploaded = await GitHub.uploadImage(file) if (uploaded) { photos.push({ 'value': uploaded }) } - } else if (files[i].alt || files[i].value) { - photos.push(files[i]) + } else if (file.alt || file.value) { + photos.push(file) } else { - photos.push({ 'value': files[i] }) + photos.push({ 'value': file }) } } return photos } +const handleUpdate = (body, parsed) => { + if (!body && !body.replace && !body.add && !body.delete) { + return + } + 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 if (body.delete && Array.isArray(body.delete)) { + for (let key of body.delete) { + delete parsed[key] + } + return parsed + } 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) @@ -31,15 +72,13 @@ export default { const uploaded = await uploadFiles(parsed.photo) if (uploaded && uploaded.length) { let imageContent = '' - for (let i in uploaded) { - const { alt, value } = uploaded[i] - if (value) { - imageContent += `![${alt || ''}](/${value})\n\n` + for (let img of uploaded) { + if (img.value) { + imageContent += `![${img.alt || ''}](/${img.value})\n\n` } } parsed.content = `${imageContent}${parsed.content}` } - console.log(parsed.content) const out = content.format(parsed) if (!out || !out.filename || !out.formatted) { return { 'error': 'could not parse data' } @@ -66,17 +105,11 @@ export default { if (!parsed) { return { 'error': 'could not parse file' } } - if (!body.replace || !body.replace.content) { - return { 'error': 'nothing to update' } + const updated = handleUpdate(body, parsed) + if (!updated) { + return { 'error': 'nothing to update' } } - const replace = utils.removeEmpty(parse.fromJSON({ - 'type': parsed.type, - 'properties': body.replace - })) - // Merge properties from `replace` into `parsed` - parsed = { ...parsed, ...replace } - - const out = content.format(parsed) + const out = content.format(updated) if (!out || !out.filename || !out.formatted) { return { 'error': 'could not parse data' } } diff --git a/src/libs/utils.js b/src/libs/utils.js index 51893f7..ffcb118 100644 --- a/src/libs/utils.js +++ b/src/libs/utils.js @@ -15,7 +15,7 @@ const utils = { removeEmpty: data => { for (let i in data) { - if (data[i] === undefined || data[i] === null) { + if (data[i] === undefined || data[i] === null || !data[i].length) { delete data[i] } } From 37311e88a57255d062910764ba382603c0b4341c Mon Sep 17 00:00:00 2001 From: Benji Encalada Mora Date: Mon, 11 Oct 2021 00:44:55 -0500 Subject: [PATCH 16/16] fix: 404 Remove a property --- src/libs/publish.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/libs/publish.js b/src/libs/publish.js index 426d405..743fb20 100644 --- a/src/libs/publish.js +++ b/src/libs/publish.js @@ -25,6 +25,12 @@ 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 @@ -34,11 +40,6 @@ const handleUpdate = (body, parsed) => { } if (body.replace) { return { ...parsed, ...updates } - } else if (body.delete && Array.isArray(body.delete)) { - for (let key of body.delete) { - delete parsed[key] - } - return parsed } else { for (let [key, value] of Object.entries(updates)) { if (key == 'type' || key == 'photo') { // skip these properties