Skip to content

Commit

Permalink
Re-architect a bunch of the record logic and add more tests. Also sta…
Browse files Browse the repository at this point in the history
…rt to use Flow for the record view logic.
  • Loading branch information
jeresig committed Mar 6, 2017
1 parent 0c2853b commit d3c0eee
Show file tree
Hide file tree
Showing 23 changed files with 1,208 additions and 709 deletions.
3 changes: 3 additions & 0 deletions .flowconfig
Expand Up @@ -2,5 +2,8 @@
module.file_ext=.js
module.file_ext=.jsx

[libs]
types/

[ignore]
<PROJECT_ROOT>/node_modules/fbjs
4 changes: 0 additions & 4 deletions src/lib/clone.js
Expand Up @@ -64,10 +64,6 @@ const cloneObject = (
};

const cloneModel = (model: {toJSON: () => Object}, i18n: i18nObject) => {
if (!model) {
return null;
}

const serialize = serializeValue(i18n);
const result = model.toJSON();

Expand Down
27 changes: 10 additions & 17 deletions src/logic/admin.js
Expand Up @@ -12,7 +12,7 @@ module.exports = function(app) {
const ImageImport = models("ImageImport");
const RecordImport = models("RecordImport");

const auth = require("./shared/auth");
const {auth, canEdit} = require("./shared/auth");

const importRecords = ({i18n, lang, query, source}, res) => {
const batchError = (err) => RecordImport.getError(i18n, err);
Expand Down Expand Up @@ -145,7 +145,7 @@ module.exports = function(app) {
const dataImport = results[1]
.sort((a, b) => b.created - a.created);
const title = i18n.format(i18n.gettext("%(name)s Admin Area"), {
name: source.getFullName,
name: source.getFullName(i18n),
});

res.render("Admin", {
Expand Down Expand Up @@ -266,24 +266,17 @@ module.exports = function(app) {

routes() {
const source = (req, res, next) => {
const {i18n, params} = req;
const {params: {source}} = req;
const Source = models("Source");

try {
req.source = Source.getSource(params.source);
next();

} catch (e) {
return res.status(404).render("Error", {
title: i18n.gettext("Source not found."),
});
}
req.source = Source.getSource(source);
next();
};

app.get("/:type/source/:source/admin", auth, source, this.admin);
app.post("/:type/source/:source/upload-images", auth, source,
this.uploadImages);
app.post("/:type/source/:source/upload-data", auth, source,
app.get("/:type/source/:source/admin", auth, canEdit, source,
this.admin);
app.post("/:type/source/:source/upload-images", auth, canEdit,
source, this.uploadImages);
app.post("/:type/source/:source/upload-data", auth, canEdit, source,
this.uploadData);
},
};
Expand Down
223 changes: 223 additions & 0 deletions src/logic/create.js
@@ -0,0 +1,223 @@
// @flow

const async = require("async");
const formidable = require("formidable");

const db = require("../lib/db");
const {cloneModel} = require("../lib/clone");
const record = require("../lib/record");
const models = require("../lib/models");
const options = require("../lib/options");
const metadata = require("../lib/metadata");

module.exports = function(app: express$Application) {
const Image = models("Image");

const {auth, canEdit} = require("./shared/auth");

const cloneView = ({
i18n,
params: {type, source, recordName},
}: express$Request, res) => {
const Record = record(type);
const model = metadata.model(type);
const id = `${source}/${recordName}`;

Record.findById(id, (err, oldRecord) => {
if (err || !oldRecord) {
return res.status(404).render("Error", {
title: i18n.gettext("Not found."),
});
}

const recordTitle = oldRecord.getTitle(i18n);
const title = i18n.format(
i18n.gettext("Cloning '%(recordTitle)s'"),
{recordTitle});

const data = {
type,
source: oldRecord.source,
lang: oldRecord.lang,
};

const cloneFields = options.types[type].cloneFields ||
Object.keys(model);

for (const typeName of cloneFields) {
data[typeName] = oldRecord[typeName];
}

const record = new Record(data);

record.loadImages(true, () => {
Record.getFacets(i18n, (err, globalFacets) => {
record.getDynamicValues(i18n, (err, dynamicValues) => {
res.render("EditRecord", {
title,
mode: "clone",
record: cloneModel(record, i18n),
globalFacets,
dynamicValues,
type,
});
});
});
});
});
};

const createView = ({
user,
params: {type},
query: {source},
i18n,
}: express$Request, res, next) => {
if (!user) {
return next();
}

const sources = user.getEditableSourcesByType()[type];

if (sources.length === 0) {
return res.status(403).render("Error", {
title: i18n.gettext("Authorization required."),
});
}

const Record = record(type);
const title = i18n.format(
i18n.gettext("%(recordName)s: Create New"), {
recordName: options.types[type].name(i18n),
});

Record.getFacets(i18n, (err, globalFacets) => {
res.render("EditRecord", {
title,
source,
sources,
mode: "create",
type,
globalFacets,
dynamicValues: {},
});
});
};

const create = (req: express$Request, res, next) => {
const {params: {type, source}, i18n, lang} = req;
const props = {};
const model = metadata.model(type);
const hasImageSearch = options.types[type].hasImageSearch();

const form = new formidable.IncomingForm();
form.encoding = "utf-8";
form.maxFieldsSize = options.maxUploadSize;
form.multiples = true;

form.parse(req, (err, fields, files) => {
/* istanbul ignore if */
if (err) {
return next(new Error(
i18n.gettext("Error processing upload.")));
}

for (const prop in model) {
props[prop] = fields[prop];
}

if (options.types[type].autoID) {
props.id = db.mongoose.Types.ObjectId().toString();
} else {
props.id = fields.id;
}

Object.assign(props, {
lang,
source,
type,
});

const Record = record(type);

const {data, error} = Record.lintData(props, i18n);

if (error) {
return next(new Error(error));
}

const newRecord = new Record(data);

const mockBatch = {
_id: db.mongoose.Types.ObjectId().toString(),
source: newRecord.source,
};

const images = Array.isArray(files.images) ?
files.images :
files.images ?
[files.images] :
[];

async.mapSeries(images, (file, callback) => {
if (!file.path || file.size <= 0) {
return process.nextTick(callback);
}

Image.fromFile(mockBatch, file, (err, image) => {
// TODO: Display better error message
if (err) {
return callback(
new Error(
i18n.gettext("Error processing image.")));
}

image.save((err) => {
/* istanbul ignore if */
if (err) {
return callback(err);
}

callback(null, image);
});
});
}, (err, unfilteredImages) => {
if (err) {
return next(err);
}

newRecord.images = unfilteredImages
.filter((image) => image)
.map((image) => image._id);

newRecord.save((err) => {
if (err) {
return next(new Error(
i18n.gettext("Error saving record.")));
}

const finish = () =>
res.redirect(newRecord.getURL(lang));

if (newRecord.images.length === 0 || !hasImageSearch) {
return finish();
}

// If new images were added then we need to update
// their similarity and the similarity of all other
// images, as well.
Image.queueBatchSimilarityUpdate(mockBatch._id, finish);
});
});
});
};

return {
routes() {
app.get("/:type/create", auth, canEdit, createView);
app.post("/:type/create", auth, canEdit, create);
app.get("/:type/:source/:recordName/clone", auth, canEdit,
cloneView);
},
};
};

0 comments on commit d3c0eee

Please sign in to comment.