Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevents overwriting with duplicate slug names #1381

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 63 additions & 18 deletions src/backends/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import TestRepoBackend from "./test-repo/implementation";
import GitHubBackend from "./github/implementation";
import GitGatewayBackend from "./git-gateway/implementation";
import { registerBackend, getBackend } from 'Lib/registry';
import { EditorialWorkflowError } from "ValueObjects/errors";

/**
* Register internal backends
Expand All @@ -24,7 +25,6 @@ registerBackend('git-gateway', GitGatewayBackend);
registerBackend('github', GitHubBackend);
registerBackend('test-repo', TestRepoBackend);


class LocalStorageAuthStore {
storageKey = "netlify-cms-user";

Expand All @@ -42,23 +42,28 @@ class LocalStorageAuthStore {
}
}

const slugFormatter = (template = "{{slug}}", entryData, slugConfig) => {
const date = new Date();

const getIdentifier = (entryData) => {
const validIdentifierFields = ["title", "path"];
const identifiers = validIdentifierFields.map((field) =>
entryData.find((_, key) => key.toLowerCase().trim() === field)
);
const validIdentifierFields = ["title", "path"];

const identifier = identifiers.find(ident => ident !== undefined);
const getIdentifierKey = entryData => {
const keys = validIdentifierFields.map(field => {
return entryData.findKey((_, key) => {
return key.toLowerCase().trim() === field;
});
});
return keys.find(key => key !== undefined);
};

if (identifier === undefined) {
throw new Error("Collection must have a field name that is a valid entry identifier");
}
const getIdentifier = entryData => {
const key = getIdentifierKey(entryData);
const identifier = entryData.get(key);
if (identifier === undefined) {
throw new Error("Collection must have a field name that is a valid entry identifier");
}
return identifier;
};

return identifier;
};
const slugFormatter = (template = "{{slug}}", entryData, slugConfig) => {
const date = new Date();

const slug = template.replace(/\{\{([^\}]+)\}\}/g, (_, field) => {
switch (field) {
Expand Down Expand Up @@ -261,7 +266,43 @@ class Backend {
.then(this.entryWithFormat(collection, slug));
}

persistEntry(config, collection, entryDraft, MediaFiles, integrations, options = {}) {
async checkOverwrite(collection, slug, path, entryData) {
const identifierKey = getIdentifierKey(entryData);
const identifierField = collection.get('fields').find(field => {
return field.get('name') === identifierKey;
});
const identifierLabel = identifierField.get('label');
const errorMessage = `\
Duplicate filename found. Please ensure the ${identifierLabel} field is unique from other entries \
in the ${collection.get('name')} collection.\
`;

const existingEntry = await this.unpublishedEntry(collection, slug).catch(error => {
if (error instanceof EditorialWorkflowError && error.notUnderEditorialWorkflow) {
return Promise.resolve(false);
}
return Promise.reject(error);
});

if (existingEntry) {
throw new Error(errorMessage);
}

const publishedEntry = await this.implementation.getEntry(collection, slug, path)
.then(({ data }) => data)
.catch(error => {
if (error.message === 'Not Found') {
return Promise.resolve(false);
}
return Promise.reject(error);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for reviewers: the catch method being added is the only new change in this PR compared to the original PR.


if (publishedEntry) {
throw new Error(errorMessage);
}
}

async persistEntry(config, collection, entryDraft, MediaFiles, integrations, options = {}) {
const newEntry = entryDraft.getIn(["entry", "newRecord"]) || false;

const parsedData = {
Expand All @@ -275,12 +316,16 @@ class Backend {
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"]), config.get("slug"));
const entryData = entryDraft.getIn(['entry', 'data']);
const slug = slugFormatter(collection.get('slug'), entryData, config.get('slug'));
const path = selectEntryPath(collection, slug);

await this.checkOverwrite(collection, slug, path, entryData);

entryObj = {
path,
slug,
raw: this.entryToRaw(collection, entryDraft.get("entry")),
raw: this.entryToRaw(collection, entryDraft.get('entry')),
};
} else {
const path = entryDraft.getIn(["entry", "path"]);
Expand Down