2016-05-26 05:38:45 +00:00
|
|
|
import slug from 'slug';
|
2016-08-15 10:51:26 +00:00
|
|
|
import _ from 'lodash';
|
2016-05-26 05:38:45 +00:00
|
|
|
import randomstring from 'randomstring';
|
2016-04-29 05:25:37 +00:00
|
|
|
import {
|
|
|
|
DataTypes,
|
|
|
|
sequelize,
|
|
|
|
} from '../sequelize';
|
2016-05-23 05:08:09 +00:00
|
|
|
import {
|
|
|
|
convertToMarkdown,
|
2016-07-24 22:32:31 +00:00
|
|
|
} from '../../frontend/utils/markdown';
|
2016-06-04 19:30:05 +00:00
|
|
|
import {
|
|
|
|
truncateMarkdown,
|
|
|
|
} from '../utils/truncate';
|
2016-05-22 14:25:13 +00:00
|
|
|
import User from './User';
|
2016-06-26 18:23:03 +00:00
|
|
|
import Revision from './Revision';
|
2016-04-29 05:25:37 +00:00
|
|
|
|
2016-07-26 07:05:10 +00:00
|
|
|
slug.defaults.mode = 'rfc3986';
|
2016-05-26 05:38:45 +00:00
|
|
|
|
2016-08-12 13:36:48 +00:00
|
|
|
const createRevision = async (doc) => {
|
|
|
|
// Create revision of the current (latest)
|
|
|
|
await Revision.create({
|
|
|
|
title: doc.title,
|
|
|
|
text: doc.text,
|
|
|
|
html: doc.html,
|
|
|
|
preview: doc.preview,
|
|
|
|
userId: doc.lastModifiedById,
|
|
|
|
documentId: doc.id,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2016-08-15 10:51:26 +00:00
|
|
|
const documentBeforeSave = async (doc) => {
|
2016-06-26 18:23:03 +00:00
|
|
|
doc.html = convertToMarkdown(doc.text);
|
|
|
|
doc.preview = truncateMarkdown(doc.text, 160);
|
2016-08-15 10:51:26 +00:00
|
|
|
|
2016-06-26 18:23:03 +00:00
|
|
|
doc.revisionCount = doc.revisionCount + 1;
|
2016-08-15 10:51:26 +00:00
|
|
|
|
|
|
|
// Collaborators
|
2016-08-18 21:42:53 +00:00
|
|
|
let ids = [];
|
|
|
|
// Only get previous user IDs if the document already exists
|
|
|
|
if (doc.id) {
|
|
|
|
ids = await Revision.findAll({
|
|
|
|
attributes: [[DataTypes.literal('DISTINCT "userId"'), 'userId']],
|
|
|
|
where: {
|
|
|
|
documentId: doc.id,
|
|
|
|
},
|
|
|
|
}).map(rev => rev.userId);
|
|
|
|
}
|
2016-08-15 10:51:26 +00:00
|
|
|
// We'll add the current user as revision hasn't been generated yet
|
|
|
|
ids.push(doc.lastModifiedById);
|
|
|
|
doc.collaboratorIds = _.uniq(ids);
|
|
|
|
|
2016-06-26 18:23:03 +00:00
|
|
|
return doc;
|
|
|
|
};
|
|
|
|
|
2016-04-29 05:25:37 +00:00
|
|
|
const Document = sequelize.define('document', {
|
|
|
|
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
|
2016-05-26 05:38:45 +00:00
|
|
|
urlId: { type: DataTypes.STRING, primaryKey: true },
|
2016-05-26 05:47:31 +00:00
|
|
|
private: { type: DataTypes.BOOLEAN, defaultValue: true },
|
2016-05-20 03:46:34 +00:00
|
|
|
title: DataTypes.STRING,
|
|
|
|
text: DataTypes.TEXT,
|
2016-05-23 05:08:09 +00:00
|
|
|
html: DataTypes.TEXT,
|
|
|
|
preview: DataTypes.TEXT,
|
2016-07-26 07:05:10 +00:00
|
|
|
revisionCount: { type: DataTypes.INTEGER, defaultValue: 0 },
|
2016-06-22 07:01:57 +00:00
|
|
|
|
|
|
|
parentDocumentId: DataTypes.UUID,
|
2016-08-15 10:51:26 +00:00
|
|
|
createdById: {
|
|
|
|
type: DataTypes.UUID,
|
|
|
|
allowNull: false,
|
|
|
|
references: {
|
|
|
|
model: 'users',
|
|
|
|
},
|
|
|
|
},
|
2016-06-26 18:23:03 +00:00
|
|
|
lastModifiedById: {
|
2016-08-15 10:51:26 +00:00
|
|
|
type: DataTypes.UUID,
|
2016-06-26 18:23:03 +00:00
|
|
|
allowNull: false,
|
|
|
|
references: {
|
|
|
|
model: 'users',
|
2016-07-26 07:05:10 +00:00
|
|
|
},
|
2016-06-26 18:23:03 +00:00
|
|
|
},
|
2016-08-15 10:51:26 +00:00
|
|
|
collaboratorIds: DataTypes.ARRAY(DataTypes.UUID),
|
2016-05-23 05:08:09 +00:00
|
|
|
}, {
|
2016-08-14 08:50:42 +00:00
|
|
|
paranoid: true,
|
2016-05-23 05:08:09 +00:00
|
|
|
hooks: {
|
2016-05-26 05:38:45 +00:00
|
|
|
beforeValidate: (doc) => {
|
2016-08-15 19:41:51 +00:00
|
|
|
doc.urlId = doc.urlId || randomstring.generate(10);
|
2016-05-26 05:38:45 +00:00
|
|
|
},
|
2016-06-26 18:23:03 +00:00
|
|
|
beforeCreate: documentBeforeSave,
|
|
|
|
beforeUpdate: documentBeforeSave,
|
2016-08-12 13:36:48 +00:00
|
|
|
afterCreate: async (doc) => await createRevision(doc),
|
|
|
|
afterUpdate: async (doc) => await createRevision(doc),
|
2016-05-26 05:38:45 +00:00
|
|
|
},
|
|
|
|
instanceMethods: {
|
2016-06-21 06:12:56 +00:00
|
|
|
getUrl() {
|
2016-08-15 19:41:51 +00:00
|
|
|
const slugifiedTitle = slug(this.title);
|
|
|
|
return `/d/${slugifiedTitle}-${this.urlId}`;
|
2016-06-21 06:12:56 +00:00
|
|
|
},
|
2016-07-26 07:05:10 +00:00
|
|
|
},
|
2016-04-29 05:25:37 +00:00
|
|
|
});
|
|
|
|
|
2016-05-22 14:25:13 +00:00
|
|
|
Document.belongsTo(User);
|
2016-04-29 05:25:37 +00:00
|
|
|
|
2016-08-23 06:37:01 +00:00
|
|
|
Document.searchForUser = async (user, query, options = {}) => {
|
|
|
|
const limit = options.limit || 15;
|
|
|
|
const offset = options.offset || 0;
|
|
|
|
|
|
|
|
const sql = `
|
|
|
|
SELECT * FROM documents
|
|
|
|
WHERE "searchVector" @@ plainto_tsquery('english', :query) AND
|
|
|
|
"teamId" = '${user.teamId}'::uuid AND
|
|
|
|
"deletedAt" IS NULL
|
2016-08-23 06:49:28 +00:00
|
|
|
ORDER BY ts_rank(documents."searchVector", plainto_tsquery('english', :query)) DESC
|
|
|
|
LIMIT :limit OFFSET :offset;
|
2016-08-23 06:37:01 +00:00
|
|
|
`;
|
|
|
|
|
|
|
|
const documents = await sequelize
|
|
|
|
.query(
|
|
|
|
sql,
|
|
|
|
{
|
|
|
|
replacements: {
|
|
|
|
query,
|
|
|
|
limit,
|
|
|
|
offset,
|
|
|
|
},
|
|
|
|
model: Document,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
return documents;
|
2016-09-11 22:48:43 +00:00
|
|
|
};
|
2016-08-23 06:37:01 +00:00
|
|
|
|
2016-05-20 03:46:34 +00:00
|
|
|
export default Document;
|