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

Prevent Overwriting Files with Same Slug Names #1239

Merged
merged 7 commits into from
May 23, 2018
Merged
82 changes: 53 additions & 29 deletions src/backends/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,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 => entryData.get(key) !== undefined);
Copy link
Contributor

@tech4him1 tech4him1 May 22, 2018

Choose a reason for hiding this comment

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

Is there a reason we can't use the old logic here, where we return the values (on line 48), instead of having to run entryData.get again here? Maybe I'm mis-reading the code.

Copy link
Contributor

Choose a reason for hiding this comment

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

const keyVals = validIdentifierFields.map(field => {
  return entryData.find((_, key) => {
    return key.toLowerCase().trim() === field;
  });
});
return keyVals.find(key => key !== undefined);

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed, I believe I was thinking we were lowercasing and trimming the return value for some reason, but that isn't the case.

};

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 @@ -234,23 +239,34 @@ class Backend {
.then(this.entryWithFormat(collection, slug));
}

async checkOverwrite(collection, slug, path, entryDraft) {
const errorMessage = "Oops, duplicate filename found. Please choose a unique name.";
const existingEntry = await this.unpublishedEntry(collection, slug).catch(error => (
(error instanceof EditorialWorkflowError && error.notUnderEditorialWorkflow) ?
Promise.resolve(false) :
Promise.reject(error)));
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));
if (existingEntry) {
throw new Error(errorMessage);
}

const publishedEntry = await this.implementation.getEntry(collection, slug, path)
.then(result => {
return result.data;
});

if (publishedEntry) throw (new Error(errorMessage));
.then(({ data }) => data);

return {path, slug, raw: this.entryToRaw(collection, entryDraft.get("entry"))};
if (publishedEntry) {
throw new Error(errorMessage);
}
}

async persistEntry(config, collection, entryDraft, MediaFiles, integrations, options = {}) {
Expand All @@ -267,9 +283,17 @@ 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);
entryObj = await this.checkOverwrite(collection, slug, path, entryDraft);

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

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