p5.js-web-editor/server/models/project.js
Andrew Nicolaou d44a058fd8 Public API: Create new project (fixes #1095) (#1106)
* Converts import script to use public API endpoints

The endpoints don't exist yet, but this is a good way to see how
the implementation of the data structures differ.

* Exposes public API endpoint to fetch user's sketches

* Implements public API delete endpoint

* Adds helper to create custom ApplicationError classes

* Adds create project endpoint that understand API's data structure

This transforms the nested tree of file data into a mongoose
Project model

* Returns '201 Created' to match API spec

* Removes 'CustomError' variable assignment as it shows up in test output

* transformFiles will return file validation errors

* Tests API project controller

* Tests toModel()

* Creates default files if no root-level .html file is provided

* Do not auto-generate a slug if it is provided

Fixes a bug where the slug was auto-generated using the sketch name,
even if a slug property had been provided.

* Validates uniqueness of slugs for projects created by the public API

* Adds tests for slug uniqueness

* Configures node's Promise implementation for mongoose (fixes warnings)

* Moves createProject tests to match controller location

* Adds support for code to ApplicationErrors

* deleteProject controller tests

* getProjectsForUser controller tests

- implements tests
- update apiKey tests to use new User mocks

* Ensure error objects have consistent property names

`message` is used as a high-level description of the errors
`detail` is optional and has an plain language explanation of the
individual errors
`errors` is an array of each individual problem from `detail` in a
machine-readable format

* Assert environment variables are provided at script start

* Version public API

* Expect "files" property to always be provided

* Fixes linting error

* Converts import script to use public API endpoints

The endpoints don't exist yet, but this is a good way to see how
the implementation of the data structures differ.

* Exposes public API endpoint to fetch user's sketches

* Implements public API delete endpoint

* Adds helper to create custom ApplicationError classes

* Adds create project endpoint that understand API's data structure

This transforms the nested tree of file data into a mongoose
Project model

* Returns '201 Created' to match API spec

* Removes 'CustomError' variable assignment as it shows up in test output

* transformFiles will return file validation errors

* Tests API project controller

* Tests toModel()

* Creates default files if no root-level .html file is provided

* Do not auto-generate a slug if it is provided

Fixes a bug where the slug was auto-generated using the sketch name,
even if a slug property had been provided.

* Validates uniqueness of slugs for projects created by the public API

* Adds tests for slug uniqueness

* Configures node's Promise implementation for mongoose (fixes warnings)

* Moves createProject tests to match controller location

* deleteProject controller tests

* Adds support for code to ApplicationErrors

* getProjectsForUser controller tests

- implements tests
- update apiKey tests to use new User mocks

* Ensure error objects have consistent property names

`message` is used as a high-level description of the errors
`detail` is optional and has an plain language explanation of the
individual errors
`errors` is an array of each individual problem from `detail` in a
machine-readable format

* Assert environment variables are provided at script start

* Version public API

* Expect "files" property to always be provided

* Fixes linting error

* Checks that authenticated user has permission to create under this namespace

Previously, the project was always created under the authenticated user's
namespace, but this not obvious behaviour.
2019-08-30 14:26:57 -04:00

91 lines
2 KiB
JavaScript

import mongoose from 'mongoose';
import shortid from 'shortid';
import slugify from 'slugify';
// Register User model as it's referenced by Project
import './user';
const { Schema } = mongoose;
const fileSchema = new Schema(
{
name: { type: String, default: 'sketch.js' },
content: { type: String, default: '' },
url: { type: String },
children: { type: [String], default: [] },
fileType: { type: String, default: 'file' },
isSelectedFile: { type: Boolean }
},
{ timestamps: true, _id: true, usePushEach: true }
);
fileSchema.virtual('id').get(function getFileId() {
return this._id.toHexString();
});
fileSchema.set('toJSON', {
virtuals: true
});
const projectSchema = new Schema(
{
name: { type: String, default: "Hello p5.js, it's the server" },
user: { type: Schema.Types.ObjectId, ref: 'User' },
serveSecure: { type: Boolean, default: false },
files: { type: [fileSchema] },
_id: { type: String, default: shortid.generate },
slug: { type: String }
},
{ timestamps: true, usePushEach: true }
);
projectSchema.virtual('id').get(function getProjectId() {
return this._id;
});
projectSchema.set('toJSON', {
virtuals: true
});
projectSchema.pre('save', function generateSlug(next) {
const project = this;
if (!project.slug) {
project.slug = slugify(project.name, '_');
}
return next();
});
/**
* Check if slug is unique for this user's projects
*/
projectSchema.methods.isSlugUnique = async function isSlugUnique(cb) {
const project = this;
const hasCallback = typeof cb === 'function';
try {
const docsWithSlug = await project.model('Project')
.find({ user: project.user, slug: project.slug }, '_id')
.exec();
const result = {
isUnique: docsWithSlug.length === 0,
conflictingIds: docsWithSlug.map(d => d._id) || []
};
if (hasCallback) {
cb(null, result);
}
return result;
} catch (err) {
if (hasCallback) {
cb(err, null);
}
throw err;
}
};
export default mongoose.model('Project', projectSchema);