import TestRepoBackend from './test-repo/implementation'; import GitHubBackend from './github/implementation'; import NetlifyGitBackend from './netlify-git/implementation'; import { resolveFormat } from '../formats/formats'; import { selectListMethod, selectEntrySlug, selectEntryPath, selectAllowNewEntries } from '../reducers/collections'; import { createEntry } from '../valueObjects/Entry'; class LocalStorageAuthStore { storageKey = 'nf-cms-user'; retrieve() { const data = window.localStorage.getItem(this.storageKey); return data && JSON.parse(data); } store(userData) { window.localStorage.setItem(this.storageKey, JSON.stringify(userData)); } logout() { window.localStorage.removeItem(this.storageKey); } } const slugFormatter = (template, entryData) => { const date = new Date(); const identifier = entryData.get('title', entryData.get('path')); return template.replace(/\{\{([^\}]+)\}\}/g, (_, field) => { switch (field) { case 'year': return date.getFullYear(); case 'month': return (`0${ date.getMonth() + 1 }`).slice(-2); case 'day': return (`0${ date.getDate() }`).slice(-2); case 'slug': return identifier.trim().toLowerCase().replace(/[^a-z0-9\.\-_]+/gi, '-'); default: return entryData.get(field); } }); }; class Backend { constructor(implementation, authStore = null) { this.implementation = implementation; this.authStore = authStore; if (this.implementation === null) { throw new Error('Cannot instantiate a Backend with no implementation'); } } currentUser() { if (this.user) { return this.user; } const stored = this.authStore && this.authStore.retrieve(); if (stored) { this.implementation.setUser(stored); return stored; } return null; } authComponent() { return this.implementation.authComponent(); } authenticate(credentials) { return this.implementation.authenticate(credentials).then((user) => { if (this.authStore) { this.authStore.store(user); } return user; }); } logout() { if (this.authStore) { this.authStore.logout(); } else { throw new Error('User isn\'t authenticated.'); } } listEntries(collection) { const listMethod = this.implementation[selectListMethod(collection)]; return listMethod.call(this.implementation, collection) .then(loadedEntries => ( loadedEntries.map(loadedEntry => createEntry( collection.get('name'), selectEntrySlug(collection, loadedEntry.file.path), loadedEntry.file.path, { raw: loadedEntry.data, label: loadedEntry.file.label } )) )) .then(entries => ( { entries: entries.map(this.entryWithFormat(collection)), } )); } getEntry(collection, slug) { return this.implementation.getEntry(collection, slug, selectEntryPath(collection, slug)) .then(loadedEntry => this.entryWithFormat(collection, slug)(createEntry( collection.get('name'), slug, loadedEntry.file.path, { raw: loadedEntry.data, label: loadedEntry.file.label } )) ); } newEntry(collection) { return createEntry(collection.get('name')); } entryWithFormat(collectionOrEntity) { return (entry) => { const format = resolveFormat(collectionOrEntity, entry); if (entry && entry.raw) { return Object.assign(entry, { data: format && format.fromFile(entry.raw) }); } return format.fromFile(entry); }; } unpublishedEntries(page, perPage) { return this.implementation.unpublishedEntries(page, perPage) .then(loadedEntries => loadedEntries.filter(entry => entry !== null)) .then(entries => ( entries.map((loadedEntry) => { const entry = createEntry('draft', loadedEntry.slug, loadedEntry.file.path, { raw: loadedEntry.data }); entry.metaData = loadedEntry.metaData; return entry; }) )) .then(entries => ({ pagination: 0, entries: entries.map(this.entryWithFormat('editorialWorkflow')), })); } unpublishedEntry(collection, slug) { return this.implementation.unpublishedEntry(collection, slug) .then((loadedEntry) => { const entry = createEntry('draft', loadedEntry.slug, loadedEntry.file.path, { raw: loadedEntry.data }); entry.metaData = loadedEntry.metaData; return entry; }) .then(this.entryWithFormat(collection, slug)); } persistEntry(config, collection, entryDraft, MediaFiles, options) { const newEntry = entryDraft.getIn(['entry', 'newRecord']) || false; const parsedData = { title: entryDraft.getIn(['entry', 'data', 'title'], 'No Title'), description: entryDraft.getIn(['entry', 'data', 'description'], 'No Description!'), }; const entryData = entryDraft.getIn(['entry', 'data']).toJS(); let entryObj; if (newEntry) { if (!selectAllowNewEntries(collection)) { throw (new Error('Not allowed to create new entries in this collection')); } const slug = slugFormatter(collection.get('slug'), entryDraft.getIn(['entry', 'data'])); const path = selectEntryPath(collection, slug); entryObj = { path, slug, raw: this.entryToRaw(collection, Object.assign({ path }, entryData)), }; } else { const path = entryDraft.getIn(['entry', 'path']); entryObj = { path, slug: entryDraft.getIn(['entry', 'slug']), raw: this.entryToRaw(collection, Object.assign({ path }, entryData)), }; } const commitMessage = `${ (newEntry ? 'Created ' : 'Updated ') + collection.get('label') } “${ entryDraft.getIn(['entry', 'data', 'title']) }”`; const mode = config.get('publish_mode'); const collectionName = collection.get('name'); return this.implementation.persistEntry(entryObj, MediaFiles, { newEntry, parsedData, commitMessage, collectionName, mode, ...options, }); } persistUnpublishedEntry(config, collection, entryDraft, MediaFiles) { return this.persistEntry(config, collection, entryDraft, MediaFiles, { unpublished: true }); } updateUnpublishedEntryStatus(collection, slug, newStatus) { return this.implementation.updateUnpublishedEntryStatus(collection, slug, newStatus); } publishUnpublishedEntry(collection, slug, status) { return this.implementation.publishUnpublishedEntry(collection, slug, status); } entryToRaw(collection, entry) { const format = resolveFormat(collection, entry); return format && format.toFile(entry); } } export function resolveBackend(config) { const name = config.getIn(['backend', 'name']); if (name == null) { throw new Error('No backend defined in configuration'); } const authStore = new LocalStorageAuthStore(); switch (name) { case 'test-repo': return new Backend(new TestRepoBackend(config), authStore); case 'github': return new Backend(new GitHubBackend(config), authStore); case 'netlify-git': return new Backend(new NetlifyGitBackend(config), authStore); default: throw new Error(`Backend not found: ${ name }`); } } export const currentBackend = (function () { let backend = null; return (config) => { if (backend) { return backend; } if (config.get('backend')) { return backend = resolveBackend(config); } }; }());