chore: Move to prettier standard double quotes (#1309)

This commit is contained in:
Tom Moor
2020-06-20 13:59:15 -07:00
committed by GitHub
parent 2a3b9e2104
commit f43deb7940
444 changed files with 5988 additions and 5977 deletions

View File

@@ -1,9 +1,9 @@
// @flow
import { DataTypes, sequelize } from '../sequelize';
import randomstring from 'randomstring';
import { DataTypes, sequelize } from "../sequelize";
import randomstring from "randomstring";
const ApiKey = sequelize.define(
'apiKeys',
"apiKeys",
{
id: {
type: DataTypes.UUID,
@@ -17,12 +17,12 @@ const ApiKey = sequelize.define(
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
model: "users",
},
},
},
{
tableName: 'apiKeys',
tableName: "apiKeys",
paranoid: true,
hooks: {
beforeValidate: key => {
@@ -34,8 +34,8 @@ const ApiKey = sequelize.define(
ApiKey.associate = models => {
ApiKey.belongsTo(models.User, {
as: 'user',
foreignKey: 'userId',
as: "user",
foreignKey: "userId",
});
};

View File

@@ -1,10 +1,10 @@
// @flow
import path from 'path';
import { DataTypes, sequelize } from '../sequelize';
import { deleteFromS3 } from '../utils/s3';
import path from "path";
import { DataTypes, sequelize } from "../sequelize";
import { deleteFromS3 } from "../utils/s3";
const Attachment = sequelize.define(
'attachment',
"attachment",
{
id: {
type: DataTypes.UUID,
@@ -30,9 +30,9 @@ const Attachment = sequelize.define(
acl: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: 'public-read',
defaultValue: "public-read",
validate: {
isIn: [['private', 'public-read']],
isIn: [["private", "public-read"]],
},
},
},
@@ -45,7 +45,7 @@ const Attachment = sequelize.define(
return `/api/attachments.redirect?id=${this.id}`;
},
isPrivate: function() {
return this.acl === 'private';
return this.acl === "private";
},
},
}

View File

@@ -1,7 +1,7 @@
// @flow
import { DataTypes, sequelize, encryptedFields } from '../sequelize';
import { DataTypes, sequelize, encryptedFields } from "../sequelize";
const Authentication = sequelize.define('authentication', {
const Authentication = sequelize.define("authentication", {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@@ -9,17 +9,17 @@ const Authentication = sequelize.define('authentication', {
},
service: DataTypes.STRING,
scopes: DataTypes.ARRAY(DataTypes.STRING),
token: encryptedFields.vault('token'),
token: encryptedFields.vault("token"),
});
Authentication.associate = models => {
Authentication.belongsTo(models.User, {
as: 'user',
foreignKey: 'userId',
as: "user",
foreignKey: "userId",
});
Authentication.belongsTo(models.Team, {
as: 'team',
foreignKey: 'teamId',
as: "team",
foreignKey: "teamId",
});
};

View File

@@ -1,7 +1,7 @@
// @flow
import { DataTypes, sequelize } from '../sequelize';
import { DataTypes, sequelize } from "../sequelize";
const Backlink = sequelize.define('backlink', {
const Backlink = sequelize.define("backlink", {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@@ -11,16 +11,16 @@ const Backlink = sequelize.define('backlink', {
Backlink.associate = models => {
Backlink.belongsTo(models.Document, {
as: 'document',
foreignKey: 'documentId',
as: "document",
foreignKey: "documentId",
});
Backlink.belongsTo(models.Document, {
as: 'reverseDocument',
foreignKey: 'reverseDocumentId',
as: "reverseDocument",
foreignKey: "reverseDocumentId",
});
Backlink.belongsTo(models.User, {
as: 'user',
foreignKey: 'userId',
as: "user",
foreignKey: "userId",
});
};

View File

@@ -1,15 +1,15 @@
// @flow
import { find, concat, remove, uniq } from 'lodash';
import slug from 'slug';
import randomstring from 'randomstring';
import { DataTypes, sequelize } from '../sequelize';
import Document from './Document';
import CollectionUser from './CollectionUser';
import { find, concat, remove, uniq } from "lodash";
import slug from "slug";
import randomstring from "randomstring";
import { DataTypes, sequelize } from "../sequelize";
import Document from "./Document";
import CollectionUser from "./CollectionUser";
slug.defaults.mode = 'rfc3986';
slug.defaults.mode = "rfc3986";
const Collection = sequelize.define(
'collection',
"collection",
{
id: {
type: DataTypes.UUID,
@@ -25,14 +25,14 @@ const Collection = sequelize.define(
maintainerApprovalRequired: DataTypes.BOOLEAN,
type: {
type: DataTypes.STRING,
validate: { isIn: [['atlas', 'journal']] },
validate: { isIn: [["atlas", "journal"]] },
},
/* type: atlas */
documentStructure: DataTypes.JSONB,
},
{
tableName: 'collections',
tableName: "collections",
paranoid: true,
hooks: {
beforeValidate: (collection: Collection) => {
@@ -47,8 +47,8 @@ const Collection = sequelize.define(
}
);
Collection.addHook('beforeSave', async model => {
if (model.icon === 'collection') {
Collection.addHook("beforeSave", async model => {
if (model.icon === "collection") {
model.icon = null;
}
});
@@ -57,48 +57,48 @@ Collection.addHook('beforeSave', async model => {
Collection.associate = models => {
Collection.hasMany(models.Document, {
as: 'documents',
foreignKey: 'collectionId',
onDelete: 'cascade',
as: "documents",
foreignKey: "collectionId",
onDelete: "cascade",
});
Collection.hasMany(models.CollectionUser, {
as: 'memberships',
foreignKey: 'collectionId',
onDelete: 'cascade',
as: "memberships",
foreignKey: "collectionId",
onDelete: "cascade",
});
Collection.hasMany(models.CollectionGroup, {
as: 'collectionGroupMemberships',
foreignKey: 'collectionId',
onDelete: 'cascade',
as: "collectionGroupMemberships",
foreignKey: "collectionId",
onDelete: "cascade",
});
Collection.belongsToMany(models.User, {
as: 'users',
as: "users",
through: models.CollectionUser,
foreignKey: 'collectionId',
foreignKey: "collectionId",
});
Collection.belongsToMany(models.Group, {
as: 'groups',
as: "groups",
through: models.CollectionGroup,
foreignKey: 'collectionId',
foreignKey: "collectionId",
});
Collection.belongsTo(models.User, {
as: 'user',
foreignKey: 'creatorId',
as: "user",
foreignKey: "creatorId",
});
Collection.belongsTo(models.Team, {
as: 'team',
as: "team",
});
Collection.addScope('withMembership', userId => ({
Collection.addScope("withMembership", userId => ({
include: [
{
model: models.CollectionUser,
as: 'memberships',
as: "memberships",
where: { userId },
required: false,
},
{
model: models.CollectionGroup,
as: 'collectionGroupMemberships',
as: "collectionGroupMemberships",
required: false,
// use of "separate" property: sequelize breaks when there are
@@ -111,11 +111,11 @@ Collection.associate = models => {
// CollectionGroup [inner join] Group [inner join] GroupUser [where] userId
include: {
model: models.Group,
as: 'group',
as: "group",
required: true,
include: {
model: models.GroupUser,
as: 'groupMemberships',
as: "groupMemberships",
required: true,
where: { userId },
},
@@ -123,16 +123,16 @@ Collection.associate = models => {
},
],
}));
Collection.addScope('withAllMemberships', {
Collection.addScope("withAllMemberships", {
include: [
{
model: models.CollectionUser,
as: 'memberships',
as: "memberships",
required: false,
},
{
model: models.CollectionGroup,
as: 'collectionGroupMemberships',
as: "collectionGroupMemberships",
required: false,
// use of "separate" property: sequelize breaks when there are
@@ -145,11 +145,11 @@ Collection.associate = models => {
// CollectionGroup [inner join] Group [inner join] GroupUser [where] userId
include: {
model: models.Group,
as: 'group',
as: "group",
required: true,
include: {
model: models.GroupUser,
as: 'groupMemberships',
as: "groupMemberships",
required: true,
},
},
@@ -158,7 +158,7 @@ Collection.associate = models => {
});
};
Collection.addHook('afterDestroy', async (model: Collection) => {
Collection.addHook("afterDestroy", async (model: Collection) => {
await Document.destroy({
where: {
collectionId: model.id,
@@ -166,7 +166,7 @@ Collection.addHook('afterDestroy', async (model: Collection) => {
});
});
Collection.addHook('afterCreate', (model: Collection, options) => {
Collection.addHook("afterCreate", (model: Collection, options) => {
if (model.private) {
return CollectionUser.findOrCreate({
where: {
@@ -174,7 +174,7 @@ Collection.addHook('afterCreate', (model: Collection, options) => {
userId: model.creatorId,
},
defaults: {
permission: 'read_write',
permission: "read_write",
createdById: model.creatorId,
},
transaction: options.transaction,
@@ -186,7 +186,7 @@ Collection.addHook('afterCreate', (model: Collection, options) => {
// get all the membership relationshps a user could have with the collection
Collection.membershipUserIds = async (collectionId: string) => {
const collection = await Collection.scope('withAllMemberships').findByPk(
const collection = await Collection.scope("withAllMemberships").findByPk(
collectionId
);

View File

@@ -1,31 +1,31 @@
/* eslint-disable flowtype/require-valid-file-annotation */
import { flushdb, seed } from '../test/support';
import { Collection, Document } from '../models';
import { flushdb, seed } from "../test/support";
import { Collection, Document } from "../models";
import {
buildUser,
buildGroup,
buildCollection,
buildTeam,
} from '../test/factories';
import uuid from 'uuid';
} from "../test/factories";
import uuid from "uuid";
beforeEach(flushdb);
beforeEach(jest.resetAllMocks);
describe('#url', () => {
test('should return correct url for the collection', () => {
const collection = new Collection({ id: '1234' });
expect(collection.url).toBe('/collections/1234');
describe("#url", () => {
test("should return correct url for the collection", () => {
const collection = new Collection({ id: "1234" });
expect(collection.url).toBe("/collections/1234");
});
});
describe('#addDocumentToStructure', async () => {
test('should add as last element without index', async () => {
describe("#addDocumentToStructure", async () => {
test("should add as last element without index", async () => {
const { collection } = await seed();
const id = uuid.v4();
const newDocument = new Document({
id,
title: 'New end node',
title: "New end node",
parentDocumentId: null,
});
@@ -34,12 +34,12 @@ describe('#addDocumentToStructure', async () => {
expect(collection.documentStructure[1].id).toBe(id);
});
test('should add with an index', async () => {
test("should add with an index", async () => {
const { collection } = await seed();
const id = uuid.v4();
const newDocument = new Document({
id,
title: 'New end node',
title: "New end node",
parentDocumentId: null,
});
@@ -48,12 +48,12 @@ describe('#addDocumentToStructure', async () => {
expect(collection.documentStructure[1].id).toBe(id);
});
test('should add as a child if with parent', async () => {
test("should add as a child if with parent", async () => {
const { collection, document } = await seed();
const id = uuid.v4();
const newDocument = new Document({
id,
title: 'New end node',
title: "New end node",
parentDocumentId: document.id,
});
@@ -64,17 +64,17 @@ describe('#addDocumentToStructure', async () => {
expect(collection.documentStructure[0].children[0].id).toBe(id);
});
test('should add as a child if with parent with index', async () => {
test("should add as a child if with parent with index", async () => {
const { collection, document } = await seed();
const newDocument = new Document({
id: uuid.v4(),
title: 'node',
title: "node",
parentDocumentId: document.id,
});
const id = uuid.v4();
const secondDocument = new Document({
id,
title: 'New start node',
title: "New start node",
parentDocumentId: document.id,
});
@@ -86,13 +86,13 @@ describe('#addDocumentToStructure', async () => {
expect(collection.documentStructure[0].children[0].id).toBe(id);
});
describe('options: documentJson', async () => {
describe("options: documentJson", async () => {
test("should append supplied json over document's own", async () => {
const { collection } = await seed();
const id = uuid.v4();
const newDocument = new Document({
id: uuid.v4(),
title: 'New end node',
title: "New end node",
parentDocumentId: null,
});
@@ -101,7 +101,7 @@ describe('#addDocumentToStructure', async () => {
children: [
{
id,
title: 'Totally fake',
title: "Totally fake",
children: [],
},
],
@@ -113,16 +113,16 @@ describe('#addDocumentToStructure', async () => {
});
});
describe('#updateDocument', () => {
describe("#updateDocument", () => {
test("should update root document's data", async () => {
const { collection, document } = await seed();
document.title = 'Updated title';
document.title = "Updated title";
await document.save();
await collection.updateDocument(document);
expect(collection.documentStructure[0].title).toBe('Updated title');
expect(collection.documentStructure[0].title).toBe("Updated title");
});
test("should update child document's data", async () => {
@@ -134,32 +134,32 @@ describe('#updateDocument', () => {
userId: collection.creatorId,
lastModifiedById: collection.creatorId,
createdById: collection.creatorId,
title: 'Child document',
text: 'content',
title: "Child document",
text: "content",
});
await collection.addDocumentToStructure(newDocument);
newDocument.title = 'Updated title';
newDocument.title = "Updated title";
await newDocument.save();
await collection.updateDocument(newDocument);
expect(collection.documentStructure[0].children[0].title).toBe(
'Updated title'
"Updated title"
);
});
});
describe('#removeDocument', () => {
test('should save if removing', async () => {
describe("#removeDocument", () => {
test("should save if removing", async () => {
const { collection, document } = await seed();
jest.spyOn(collection, 'save');
jest.spyOn(collection, "save");
await collection.deleteDocument(document);
expect(collection.save).toBeCalled();
});
test('should remove documents from root', async () => {
test("should remove documents from root", async () => {
const { collection, document } = await seed();
await collection.deleteDocument(document);
@@ -174,7 +174,7 @@ describe('#removeDocument', () => {
expect(collectionDocuments.count).toBe(0);
});
test('should remove a document with child documents', async () => {
test("should remove a document with child documents", async () => {
const { collection, document } = await seed();
// Add a child for testing
@@ -185,8 +185,8 @@ describe('#removeDocument', () => {
userId: collection.creatorId,
lastModifiedById: collection.creatorId,
createdById: collection.creatorId,
title: 'Child document',
text: 'content',
title: "Child document",
text: "content",
});
await collection.addDocumentToStructure(newDocument);
expect(collection.documentStructure[0].children.length).toBe(1);
@@ -202,7 +202,7 @@ describe('#removeDocument', () => {
expect(collectionDocuments.count).toBe(0);
});
test('should remove a child document', async () => {
test("should remove a child document", async () => {
const { collection, document } = await seed();
// Add a child for testing
@@ -214,8 +214,8 @@ describe('#removeDocument', () => {
lastModifiedById: collection.creatorId,
createdById: collection.creatorId,
publishedAt: new Date(),
title: 'Child document',
text: 'content',
title: "Child document",
text: "content",
});
await collection.addDocumentToStructure(newDocument);
expect(collection.documentStructure.length).toBe(1);
@@ -236,8 +236,8 @@ describe('#removeDocument', () => {
});
});
describe('#membershipUserIds', () => {
test('should return collection and group memberships', async () => {
describe("#membershipUserIds", () => {
test("should return collection and group memberships", async () => {
const team = await buildTeam();
const teamId = team.id;

View File

@@ -1,14 +1,14 @@
// @flow
import { DataTypes, sequelize } from '../sequelize';
import { DataTypes, sequelize } from "../sequelize";
const CollectionGroup = sequelize.define(
'collection_group',
"collection_group",
{
permission: {
type: DataTypes.STRING,
defaultValue: 'read_write',
defaultValue: "read_write",
validate: {
isIn: [['read', 'read_write', 'maintainer']],
isIn: [["read", "read_write", "maintainer"]],
},
},
},
@@ -20,18 +20,18 @@ const CollectionGroup = sequelize.define(
CollectionGroup.associate = models => {
CollectionGroup.belongsTo(models.Collection, {
as: 'collection',
foreignKey: 'collectionId',
as: "collection",
foreignKey: "collectionId",
primary: true,
});
CollectionGroup.belongsTo(models.Group, {
as: 'group',
foreignKey: 'groupId',
as: "group",
foreignKey: "groupId",
primary: true,
});
CollectionGroup.belongsTo(models.User, {
as: 'createdBy',
foreignKey: 'createdById',
as: "createdBy",
foreignKey: "createdById",
});
};

View File

@@ -1,14 +1,14 @@
// @flow
import { DataTypes, sequelize } from '../sequelize';
import { DataTypes, sequelize } from "../sequelize";
const CollectionUser = sequelize.define(
'collection_user',
"collection_user",
{
permission: {
type: DataTypes.STRING,
defaultValue: 'read_write',
defaultValue: "read_write",
validate: {
isIn: [['read', 'read_write', 'maintainer']],
isIn: [["read", "read_write", "maintainer"]],
},
},
},
@@ -19,16 +19,16 @@ const CollectionUser = sequelize.define(
CollectionUser.associate = models => {
CollectionUser.belongsTo(models.Collection, {
as: 'collection',
foreignKey: 'collectionId',
as: "collection",
foreignKey: "collectionId",
});
CollectionUser.belongsTo(models.User, {
as: 'user',
foreignKey: 'userId',
as: "user",
foreignKey: "userId",
});
CollectionUser.belongsTo(models.User, {
as: 'createdBy',
foreignKey: 'createdById',
as: "createdBy",
foreignKey: "createdById",
});
};

View File

@@ -1,17 +1,17 @@
// @flow
import { map, find, compact, uniq } from 'lodash';
import MarkdownSerializer from 'slate-md-serializer';
import randomstring from 'randomstring';
import Sequelize, { type Transaction } from 'sequelize';
import removeMarkdown from '@tommoor/remove-markdown';
import { map, find, compact, uniq } from "lodash";
import MarkdownSerializer from "slate-md-serializer";
import randomstring from "randomstring";
import Sequelize, { type Transaction } from "sequelize";
import removeMarkdown from "@tommoor/remove-markdown";
import isUUID from 'validator/lib/isUUID';
import { Collection, User } from '../models';
import { DataTypes, sequelize } from '../sequelize';
import parseTitle from '../../shared/utils/parseTitle';
import unescape from '../../shared/utils/unescape';
import slugify from '../utils/slugify';
import Revision from './Revision';
import isUUID from "validator/lib/isUUID";
import { Collection, User } from "../models";
import { DataTypes, sequelize } from "../sequelize";
import parseTitle from "../../shared/utils/parseTitle";
import unescape from "../../shared/utils/unescape";
import slugify from "../utils/slugify";
import Revision from "./Revision";
const Op = Sequelize.Op;
const URL_REGEX = /^[0-9a-zA-Z-_~]*-([a-zA-Z0-9]{10,15})$/;
@@ -25,8 +25,8 @@ const createRevision = (doc, options = {}) => {
// we don't create revisions if identical to previous
if (
doc.text === doc.previous('text') &&
doc.title === doc.previous('title')
doc.text === doc.previous("text") &&
doc.title === doc.previous("title")
) {
return;
}
@@ -64,7 +64,7 @@ const beforeSave = async doc => {
doc.emoji = emoji;
// ensure documents have a title
doc.title = doc.title || '';
doc.title = doc.title || "";
// add the current user as a collaborator on this doc
if (!doc.collaboratorIds) doc.collaboratorIds = [];
@@ -77,7 +77,7 @@ const beforeSave = async doc => {
};
const Document = sequelize.define(
'document',
"document",
{
id: {
type: DataTypes.UUID,
@@ -93,7 +93,7 @@ const Document = sequelize.define(
validate: {
len: {
args: [0, 100],
msg: 'Document title must be less than 100 characters',
msg: "Document title must be less than 100 characters",
},
},
},
@@ -134,45 +134,45 @@ const Document = sequelize.define(
Document.associate = models => {
Document.belongsTo(models.Collection, {
as: 'collection',
foreignKey: 'collectionId',
onDelete: 'cascade',
as: "collection",
foreignKey: "collectionId",
onDelete: "cascade",
});
Document.belongsTo(models.Team, {
as: 'team',
foreignKey: 'teamId',
as: "team",
foreignKey: "teamId",
});
Document.belongsTo(models.User, {
as: 'createdBy',
foreignKey: 'createdById',
as: "createdBy",
foreignKey: "createdById",
});
Document.belongsTo(models.User, {
as: 'updatedBy',
foreignKey: 'lastModifiedById',
as: "updatedBy",
foreignKey: "lastModifiedById",
});
Document.belongsTo(models.User, {
as: 'pinnedBy',
foreignKey: 'pinnedById',
as: "pinnedBy",
foreignKey: "pinnedById",
});
Document.hasMany(models.Revision, {
as: 'revisions',
onDelete: 'cascade',
as: "revisions",
onDelete: "cascade",
});
Document.hasMany(models.Backlink, {
as: 'backlinks',
onDelete: 'cascade',
as: "backlinks",
onDelete: "cascade",
});
Document.hasMany(models.Star, {
as: 'starred',
onDelete: 'cascade',
as: "starred",
onDelete: "cascade",
});
Document.hasMany(models.View, {
as: 'views',
as: "views",
});
Document.addScope('defaultScope', {
Document.addScope("defaultScope", {
include: [
{ model: models.User, as: 'createdBy', paranoid: false },
{ model: models.User, as: 'updatedBy', paranoid: false },
{ model: models.User, as: "createdBy", paranoid: false },
{ model: models.User, as: "updatedBy", paranoid: false },
],
where: {
publishedAt: {
@@ -180,38 +180,38 @@ Document.associate = models => {
},
},
});
Document.addScope('withCollection', userId => {
Document.addScope("withCollection", userId => {
if (userId) {
return {
include: [
{
model: models.Collection.scope({
method: ['withMembership', userId],
method: ["withMembership", userId],
}),
as: 'collection',
as: "collection",
},
],
};
}
return {
include: [{ model: models.Collection, as: 'collection' }],
include: [{ model: models.Collection, as: "collection" }],
};
});
Document.addScope('withUnpublished', {
Document.addScope("withUnpublished", {
include: [
{ model: models.User, as: 'createdBy', paranoid: false },
{ model: models.User, as: 'updatedBy', paranoid: false },
{ model: models.User, as: "createdBy", paranoid: false },
{ model: models.User, as: "updatedBy", paranoid: false },
],
});
Document.addScope('withViews', userId => ({
Document.addScope("withViews", userId => ({
include: [
{ model: models.View, as: 'views', where: { userId }, required: false },
{ model: models.View, as: "views", where: { userId }, required: false },
],
}));
Document.addScope('withStarred', userId => ({
Document.addScope("withStarred", userId => ({
include: [
{ model: models.Star, as: 'starred', where: { userId }, required: false },
{ model: models.Star, as: "starred", where: { userId }, required: false },
],
}));
};
@@ -219,8 +219,8 @@ Document.associate = models => {
Document.findByPk = async function(id, options = {}) {
// allow default preloading of collection membership if `userId` is passed in find options
// almost every endpoint needs the collection membership to determine policy permissions.
const scope = this.scope('withUnpublished', {
method: ['withCollection', options.userId],
const scope = this.scope("withUnpublished", {
method: ["withCollection", options.userId],
});
if (isUUID(id)) {
@@ -248,7 +248,7 @@ type SearchOptions = {
limit?: number,
offset?: number,
collectionId?: string,
dateFilter?: 'day' | 'week' | 'month' | 'year',
dateFilter?: "day" | "week" | "month" | "year",
collaboratorIds?: string[],
includeArchived?: boolean,
includeDrafts?: boolean,
@@ -257,7 +257,7 @@ type SearchOptions = {
function escape(query: string): string {
// replace "\" with escaped "\\" because sequelize.escape doesn't do it
// https://github.com/sequelize/sequelize/issues/2950
return sequelize.escape(query).replace('\\', '\\\\');
return sequelize.escape(query).replace("\\", "\\\\");
}
Document.searchForTeam = async (
@@ -308,12 +308,12 @@ Document.searchForTeam = async (
// Final query to get associated document data
const documents = await Document.findAll({
where: {
id: map(results, 'id'),
id: map(results, "id"),
},
include: [
{ model: Collection, as: 'collection' },
{ model: User, as: 'createdBy', paranoid: false },
{ model: User, as: 'updatedBy', paranoid: false },
{ model: Collection, as: "collection" },
{ model: User, as: "createdBy", paranoid: false },
{ model: User, as: "updatedBy", paranoid: false },
],
});
@@ -366,14 +366,14 @@ Document.searchForUser = async (
"teamId" = :teamId AND
"collectionId" IN(:collectionIds) AND
${
options.dateFilter ? '"updatedAt" > now() - interval :dateFilter AND' : ''
options.dateFilter ? '"updatedAt" > now() - interval :dateFilter AND' : ""
}
${
options.collaboratorIds
? '"collaboratorIds" @> ARRAY[:collaboratorIds]::uuid[] AND'
: ''
: ""
}
${options.includeArchived ? '' : '"archivedAt" IS NULL AND'}
${options.includeArchived ? "" : '"archivedAt" IS NULL AND'}
"deletedAt" IS NULL AND
${
options.includeDrafts
@@ -404,18 +404,18 @@ Document.searchForUser = async (
// Final query to get associated document data
const documents = await Document.scope(
{
method: ['withViews', user.id],
method: ["withViews", user.id],
},
{
method: ['withCollection', user.id],
method: ["withCollection", user.id],
}
).findAll({
where: {
id: map(results, 'id'),
id: map(results, "id"),
},
include: [
{ model: User, as: 'createdBy', paranoid: false },
{ model: User, as: 'updatedBy', paranoid: false },
{ model: User, as: "createdBy", paranoid: false },
{ model: User, as: "updatedBy", paranoid: false },
],
});
@@ -430,21 +430,21 @@ Document.searchForUser = async (
// Hooks
Document.addHook('beforeSave', async model => {
Document.addHook("beforeSave", async model => {
if (!model.publishedAt) return;
const collection = await Collection.findByPk(model.collectionId);
if (!collection || collection.type !== 'atlas') return;
if (!collection || collection.type !== "atlas") return;
await collection.updateDocument(model);
model.collection = collection;
});
Document.addHook('afterCreate', async model => {
Document.addHook("afterCreate", async model => {
if (!model.publishedAt) return;
const collection = await Collection.findByPk(model.collectionId);
if (!collection || collection.type !== 'atlas') return;
if (!collection || collection.type !== "atlas") return;
await collection.addDocumentToStructure(model);
model.collection = collection;
@@ -470,7 +470,7 @@ Document.prototype.migrateVersion = function() {
// migrate from document version 0 -> 1
if (!this.version) {
// removing the title from the document text attribute
this.text = this.text.replace(/^#\s(.*)\n/, '');
this.text = this.text.replace(/^#\s(.*)\n/, "");
this.version = 1;
migrated = true;
}
@@ -535,7 +535,7 @@ Document.prototype.publish = async function(options) {
if (this.publishedAt) return this.save(options);
const collection = await Collection.findByPk(this.collectionId);
if (collection.type !== 'atlas') return this.save(options);
if (collection.type !== "atlas") return this.save(options);
await collection.addDocumentToStructure(this);
@@ -618,14 +618,14 @@ Document.prototype.getSummary = function() {
const plain = removeMarkdown(unescape(this.text), {
stripHTML: false,
});
const lines = compact(plain.split('\n'));
const lines = compact(plain.split("\n"));
const notEmpty = lines.length >= 1;
if (this.version) {
return notEmpty ? lines[0] : '';
return notEmpty ? lines[0] : "";
}
return notEmpty ? lines[1] : '';
return notEmpty ? lines[1] : "";
};
Document.prototype.toJSON = function() {

View File

@@ -1,13 +1,13 @@
/* eslint-disable flowtype/require-valid-file-annotation */
import { flushdb } from '../test/support';
import { Document } from '../models';
import { buildDocument, buildCollection, buildTeam } from '../test/factories';
import { flushdb } from "../test/support";
import { Document } from "../models";
import { buildDocument, buildCollection, buildTeam } from "../test/factories";
beforeEach(flushdb);
beforeEach(jest.resetAllMocks);
describe('#getSummary', () => {
test('should strip markdown', async () => {
describe("#getSummary", () => {
test("should strip markdown", async () => {
const document = await buildDocument({
version: 1,
text: `*paragraph*
@@ -15,10 +15,10 @@ describe('#getSummary', () => {
paragraph 2`,
});
expect(document.getSummary()).toBe('paragraph');
expect(document.getSummary()).toBe("paragraph");
});
test('should strip title when no version', async () => {
test("should strip title when no version", async () => {
const document = await buildDocument({
version: null,
text: `# Heading
@@ -26,12 +26,12 @@ paragraph 2`,
*paragraph*`,
});
expect(document.getSummary()).toBe('paragraph');
expect(document.getSummary()).toBe("paragraph");
});
});
describe('#migrateVersion', () => {
test('should maintain empty paragraph under headings', async () => {
describe("#migrateVersion", () => {
test("should maintain empty paragraph under headings", async () => {
const document = await buildDocument({
version: 1,
text: `# Heading
@@ -44,7 +44,7 @@ paragraph`,
paragraph`);
});
test('should add breaks under headings with extra paragraphs', async () => {
test("should add breaks under headings with extra paragraphs", async () => {
const document = await buildDocument({
version: 1,
text: `# Heading
@@ -60,7 +60,7 @@ paragraph`,
paragraph`);
});
test('should add breaks between paragraphs', async () => {
test("should add breaks between paragraphs", async () => {
const document = await buildDocument({
version: 1,
text: `paragraph
@@ -74,7 +74,7 @@ paragraph`,
paragraph`);
});
test('should add breaks for multiple empty paragraphs', async () => {
test("should add breaks for multiple empty paragraphs", async () => {
const document = await buildDocument({
version: 1,
text: `paragraph
@@ -90,7 +90,7 @@ paragraph`,
paragraph`);
});
test('should add breaks with non-latin characters', async () => {
test("should add breaks with non-latin characters", async () => {
const document = await buildDocument({
version: 1,
text: `除。
@@ -104,7 +104,7 @@ paragraph`);
`);
});
test('should update task list formatting', async () => {
test("should update task list formatting", async () => {
const document = await buildDocument({
version: 1,
text: `[ ] list item
@@ -115,7 +115,7 @@ paragraph`);
`);
});
test('should update task list with multiple items', async () => {
test("should update task list with multiple items", async () => {
const document = await buildDocument({
version: 1,
text: `[ ] list item
@@ -128,7 +128,7 @@ paragraph`);
`);
});
test('should update checked task list formatting', async () => {
test("should update checked task list formatting", async () => {
const document = await buildDocument({
version: 1,
text: `[x] list item
@@ -139,7 +139,7 @@ paragraph`);
`);
});
test('should update nested task list formatting', async () => {
test("should update nested task list formatting", async () => {
const document = await buildDocument({
version: 1,
text: `[x] list item
@@ -155,22 +155,22 @@ paragraph`);
});
});
describe('#searchForTeam', () => {
test('should return search results from public collections', async () => {
describe("#searchForTeam", () => {
test("should return search results from public collections", async () => {
const team = await buildTeam();
const collection = await buildCollection({ teamId: team.id });
const document = await buildDocument({
teamId: team.id,
collectionId: collection.id,
title: 'test',
title: "test",
});
const results = await Document.searchForTeam(team, 'test');
const results = await Document.searchForTeam(team, "test");
expect(results.length).toBe(1);
expect(results[0].document.id).toBe(document.id);
});
test('should not return search results from private collections', async () => {
test("should not return search results from private collections", async () => {
const team = await buildTeam();
const collection = await buildCollection({
private: true,
@@ -179,16 +179,16 @@ describe('#searchForTeam', () => {
await buildDocument({
teamId: team.id,
collectionId: collection.id,
title: 'test',
title: "test",
});
const results = await Document.searchForTeam(team, 'test');
const results = await Document.searchForTeam(team, "test");
expect(results.length).toBe(0);
});
test('should handle no collections', async () => {
test("should handle no collections", async () => {
const team = await buildTeam();
const results = await Document.searchForTeam(team, 'test');
const results = await Document.searchForTeam(team, "test");
expect(results.length).toBe(0);
});
});

View File

@@ -1,8 +1,8 @@
// @flow
import { DataTypes, sequelize } from '../sequelize';
import events from '../events';
import { DataTypes, sequelize } from "../sequelize";
import events from "../events";
const Event = sequelize.define('event', {
const Event = sequelize.define("event", {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@@ -16,31 +16,31 @@ const Event = sequelize.define('event', {
Event.associate = models => {
Event.belongsTo(models.User, {
as: 'user',
foreignKey: 'userId',
as: "user",
foreignKey: "userId",
});
Event.belongsTo(models.User, {
as: 'actor',
foreignKey: 'actorId',
as: "actor",
foreignKey: "actorId",
});
Event.belongsTo(models.Collection, {
as: 'collection',
foreignKey: 'collectionId',
as: "collection",
foreignKey: "collectionId",
});
Event.belongsTo(models.Collection, {
as: 'document',
foreignKey: 'documentId',
as: "document",
foreignKey: "documentId",
});
Event.belongsTo(models.Team, {
as: 'team',
foreignKey: 'teamId',
as: "team",
foreignKey: "teamId",
});
};
Event.beforeCreate(event => {
if (event.ip) {
// cleanup IPV6 representations of IPV4 addresses
event.ip = event.ip.replace(/^::ffff:/, '');
event.ip = event.ip.replace(/^::ffff:/, "");
}
});
@@ -49,48 +49,48 @@ Event.afterCreate(event => {
});
Event.ACTIVITY_EVENTS = [
'users.create',
'documents.publish',
'documents.archive',
'documents.unarchive',
'documents.pin',
'documents.unpin',
'documents.delete',
'documents.restore',
'collections.create',
'collections.delete',
"users.create",
"documents.publish",
"documents.archive",
"documents.unarchive",
"documents.pin",
"documents.unpin",
"documents.delete",
"documents.restore",
"collections.create",
"collections.delete",
];
Event.AUDIT_EVENTS = [
'api_keys.create',
'api_keys.delete',
'users.create',
'users.promote',
'users.demote',
'users.invite',
'users.suspend',
'users.activate',
'users.delete',
'documents.publish',
'documents.update',
'documents.archive',
'documents.unarchive',
'documents.pin',
'documents.unpin',
'documents.move',
'documents.delete',
'shares.create',
'shares.revoke',
'groups.create',
'groups.update',
'groups.delete',
'collections.create',
'collections.update',
'collections.add_user',
'collections.remove_user',
'collections.add_group',
'collections.remove_group',
'collections.delete',
"api_keys.create",
"api_keys.delete",
"users.create",
"users.promote",
"users.demote",
"users.invite",
"users.suspend",
"users.activate",
"users.delete",
"documents.publish",
"documents.update",
"documents.archive",
"documents.unarchive",
"documents.pin",
"documents.unpin",
"documents.move",
"documents.delete",
"shares.create",
"shares.revoke",
"groups.create",
"groups.update",
"groups.delete",
"collections.create",
"collections.update",
"collections.add_user",
"collections.remove_user",
"collections.add_group",
"collections.remove_group",
"collections.delete",
];
export default Event;

View File

@@ -1,9 +1,9 @@
// @flow
import { Op, DataTypes, sequelize } from '../sequelize';
import { CollectionGroup, GroupUser } from '../models';
import { Op, DataTypes, sequelize } from "../sequelize";
import { CollectionGroup, GroupUser } from "../models";
const Group = sequelize.define(
'group',
"group",
{
id: {
type: DataTypes.UUID,
@@ -32,7 +32,7 @@ const Group = sequelize.define(
},
});
if (foundItem) {
throw new Error('The name of this group is already in use');
throw new Error("The name of this group is already in use");
}
},
},
@@ -41,39 +41,39 @@ const Group = sequelize.define(
Group.associate = models => {
Group.hasMany(models.GroupUser, {
as: 'groupMemberships',
foreignKey: 'groupId',
as: "groupMemberships",
foreignKey: "groupId",
});
Group.hasMany(models.CollectionGroup, {
as: 'collectionGroupMemberships',
foreignKey: 'groupId',
as: "collectionGroupMemberships",
foreignKey: "groupId",
});
Group.belongsTo(models.Team, {
as: 'team',
foreignKey: 'teamId',
as: "team",
foreignKey: "teamId",
});
Group.belongsTo(models.User, {
as: 'createdBy',
foreignKey: 'createdById',
as: "createdBy",
foreignKey: "createdById",
});
Group.belongsToMany(models.User, {
as: 'users',
as: "users",
through: models.GroupUser,
foreignKey: 'groupId',
foreignKey: "groupId",
});
Group.addScope('defaultScope', {
Group.addScope("defaultScope", {
include: [
{
association: 'groupMemberships',
association: "groupMemberships",
required: false,
},
],
order: [['name', 'ASC']],
order: [["name", "ASC"]],
});
};
// Cascade deletes to group and collection relations
Group.addHook('afterDestroy', async (group, options) => {
Group.addHook("afterDestroy", async (group, options) => {
if (!group.deletedAt) return;
await GroupUser.destroy({ where: { groupId: group.id } });

View File

@@ -1,13 +1,13 @@
/* eslint-disable flowtype/require-valid-file-annotation */
import { flushdb } from '../test/support';
import { CollectionGroup, GroupUser } from '../models';
import { buildUser, buildGroup, buildCollection } from '../test/factories';
import { flushdb } from "../test/support";
import { CollectionGroup, GroupUser } from "../models";
import { buildUser, buildGroup, buildCollection } from "../test/factories";
beforeEach(flushdb);
beforeEach(jest.resetAllMocks);
describe('afterDestroy hook', () => {
test('should destroy associated group and collection join relations', async () => {
describe("afterDestroy hook", () => {
test("should destroy associated group and collection join relations", async () => {
const group = await buildGroup();
const teamId = group.teamId;

View File

@@ -1,8 +1,8 @@
// @flow
import { sequelize } from '../sequelize';
import { sequelize } from "../sequelize";
const GroupUser = sequelize.define(
'group_user',
"group_user",
{},
{
timestamps: true,
@@ -12,21 +12,21 @@ const GroupUser = sequelize.define(
GroupUser.associate = models => {
GroupUser.belongsTo(models.Group, {
as: 'group',
foreignKey: 'groupId',
as: "group",
foreignKey: "groupId",
primary: true,
});
GroupUser.belongsTo(models.User, {
as: 'user',
foreignKey: 'userId',
as: "user",
foreignKey: "userId",
primary: true,
});
GroupUser.belongsTo(models.User, {
as: 'createdBy',
foreignKey: 'createdById',
as: "createdBy",
foreignKey: "createdById",
});
GroupUser.addScope('defaultScope', {
include: [{ association: 'user' }],
GroupUser.addScope("defaultScope", {
include: [{ association: "user" }],
});
};

View File

@@ -1,7 +1,7 @@
// @flow
import { DataTypes, sequelize } from '../sequelize';
import { DataTypes, sequelize } from "../sequelize";
const Integration = sequelize.define('integration', {
const Integration = sequelize.define("integration", {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@@ -15,20 +15,20 @@ const Integration = sequelize.define('integration', {
Integration.associate = models => {
Integration.belongsTo(models.User, {
as: 'user',
foreignKey: 'userId',
as: "user",
foreignKey: "userId",
});
Integration.belongsTo(models.Team, {
as: 'team',
foreignKey: 'teamId',
as: "team",
foreignKey: "teamId",
});
Integration.belongsTo(models.Collection, {
as: 'collection',
foreignKey: 'collectionId',
as: "collection",
foreignKey: "collectionId",
});
Integration.belongsTo(models.Authentication, {
as: 'authentication',
foreignKey: 'authenticationId',
as: "authentication",
foreignKey: "authenticationId",
});
};

View File

@@ -1,8 +1,8 @@
// @flow
import { DataTypes, sequelize } from '../sequelize';
import { DataTypes, sequelize } from "../sequelize";
const Notification = sequelize.define(
'notification',
"notification",
{
id: {
type: DataTypes.UUID,
@@ -24,12 +24,12 @@ const Notification = sequelize.define(
Notification.associate = models => {
Notification.belongsTo(models.User, {
as: 'actor',
foreignKey: 'actorId',
as: "actor",
foreignKey: "actorId",
});
Notification.belongsTo(models.User, {
as: 'user',
foreignKey: 'userId',
as: "user",
foreignKey: "userId",
});
};

View File

@@ -1,9 +1,9 @@
// @flow
import crypto from 'crypto';
import { DataTypes, sequelize } from '../sequelize';
import crypto from "crypto";
import { DataTypes, sequelize } from "../sequelize";
const NotificationSetting = sequelize.define(
'notification_setting',
"notification_setting",
{
id: {
type: DataTypes.UUID,
@@ -15,11 +15,11 @@ const NotificationSetting = sequelize.define(
validate: {
isIn: [
[
'documents.publish',
'documents.update',
'collections.create',
'emails.onboarding',
'emails.features',
"documents.publish",
"documents.update",
"collections.create",
"emails.onboarding",
"emails.features",
],
],
},
@@ -43,20 +43,20 @@ const NotificationSetting = sequelize.define(
);
NotificationSetting.getUnsubscribeToken = userId => {
const hash = crypto.createHash('sha256');
const hash = crypto.createHash("sha256");
hash.update(`${userId}-${process.env.SECRET_KEY}`);
return hash.digest('hex');
return hash.digest("hex");
};
NotificationSetting.associate = models => {
NotificationSetting.belongsTo(models.User, {
as: 'user',
foreignKey: 'userId',
onDelete: 'cascade',
as: "user",
foreignKey: "userId",
onDelete: "cascade",
});
NotificationSetting.belongsTo(models.Team, {
as: 'team',
foreignKey: 'teamId',
as: "team",
foreignKey: "teamId",
});
};

View File

@@ -1,10 +1,10 @@
// @flow
import { DataTypes, sequelize } from '../sequelize';
import MarkdownSerializer from 'slate-md-serializer';
import { DataTypes, sequelize } from "../sequelize";
import MarkdownSerializer from "slate-md-serializer";
const serializer = new MarkdownSerializer();
const Revision = sequelize.define('revision', {
const Revision = sequelize.define("revision", {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@@ -23,18 +23,18 @@ const Revision = sequelize.define('revision', {
Revision.associate = models => {
Revision.belongsTo(models.Document, {
as: 'document',
foreignKey: 'documentId',
onDelete: 'cascade',
as: "document",
foreignKey: "documentId",
onDelete: "cascade",
});
Revision.belongsTo(models.User, {
as: 'user',
foreignKey: 'userId',
as: "user",
foreignKey: "userId",
});
Revision.addScope(
'defaultScope',
"defaultScope",
{
include: [{ model: models.User, as: 'user', paranoid: false }],
include: [{ model: models.User, as: "user", paranoid: false }],
},
{ override: true }
);
@@ -46,7 +46,7 @@ Revision.prototype.migrateVersion = function() {
// migrate from document version 0 -> 1
if (!this.version) {
// removing the title from the document text attribute
this.text = this.text.replace(/^#\s(.*)\n/, '');
this.text = this.text.replace(/^#\s(.*)\n/, "");
this.version = 1;
migrated = true;
}

View File

@@ -1,8 +1,8 @@
// @flow
import { DataTypes, sequelize } from '../sequelize';
import { DataTypes, sequelize } from "../sequelize";
const Share = sequelize.define(
'share',
"share",
{
id: {
type: DataTypes.UUID,
@@ -23,16 +23,16 @@ const Share = sequelize.define(
Share.associate = models => {
Share.belongsTo(models.User, {
as: 'user',
foreignKey: 'userId',
as: "user",
foreignKey: "userId",
});
Share.belongsTo(models.Team, {
as: 'team',
foreignKey: 'teamId',
as: "team",
foreignKey: "teamId",
});
Share.belongsTo(models.Document, {
as: 'document',
foreignKey: 'documentId',
as: "document",
foreignKey: "documentId",
});
};

View File

@@ -1,7 +1,7 @@
// @flow
import { DataTypes, sequelize } from '../sequelize';
import { DataTypes, sequelize } from "../sequelize";
const Star = sequelize.define('star', {
const Star = sequelize.define("star", {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,

View File

@@ -1,25 +1,25 @@
// @flow
import uuid from 'uuid';
import { URL } from 'url';
import fs from 'fs';
import util from 'util';
import path from 'path';
import { DataTypes, sequelize, Op } from '../sequelize';
import { publicS3Endpoint, uploadToS3FromUrl } from '../utils/s3';
import uuid from "uuid";
import { URL } from "url";
import fs from "fs";
import util from "util";
import path from "path";
import { DataTypes, sequelize, Op } from "../sequelize";
import { publicS3Endpoint, uploadToS3FromUrl } from "../utils/s3";
import {
stripSubdomain,
RESERVED_SUBDOMAINS,
} from '../../shared/utils/domains';
import { ValidationError } from '../errors';
} from "../../shared/utils/domains";
import { ValidationError } from "../errors";
import Collection from './Collection';
import Document from './Document';
import User from './User';
import Collection from "./Collection";
import Document from "./Document";
import User from "./User";
const readFile = util.promisify(fs.readFile);
const Team = sequelize.define(
'team',
"team",
{
id: {
type: DataTypes.UUID,
@@ -33,16 +33,16 @@ const Team = sequelize.define(
validate: {
isLowercase: true,
is: {
args: [/^[a-z\d-]+$/, 'i'],
msg: 'Must be only alphanumeric and dashes',
args: [/^[a-z\d-]+$/, "i"],
msg: "Must be only alphanumeric and dashes",
},
len: {
args: [4, 32],
msg: 'Must be between 4 and 32 characters',
msg: "Must be between 4 and 32 characters",
},
notIn: {
args: [RESERVED_SUBDOMAINS],
msg: 'You chose a restricted word, please try another.',
msg: "You chose a restricted word, please try another.",
},
},
unique: true,
@@ -66,13 +66,13 @@ const Team = sequelize.define(
{
getterMethods: {
url() {
if (!this.subdomain || process.env.SUBDOMAINS_ENABLED !== 'true') {
if (!this.subdomain || process.env.SUBDOMAINS_ENABLED !== "true") {
return process.env.URL;
}
const url = new URL(process.env.URL);
url.host = `${this.subdomain}.${stripSubdomain(url.host)}`;
return url.href.replace(/\/$/, '');
return url.href.replace(/\/$/, "");
},
logoUrl() {
return (
@@ -84,9 +84,9 @@ const Team = sequelize.define(
);
Team.associate = models => {
Team.hasMany(models.Collection, { as: 'collections' });
Team.hasMany(models.Document, { as: 'documents' });
Team.hasMany(models.User, { as: 'users' });
Team.hasMany(models.Collection, { as: "collections" });
Team.hasMany(models.Document, { as: "documents" });
Team.hasMany(models.User, { as: "users" });
};
const uploadAvatar = async model => {
@@ -95,14 +95,14 @@ const uploadAvatar = async model => {
if (
avatarUrl &&
!avatarUrl.startsWith('/api') &&
!avatarUrl.startsWith("/api") &&
!avatarUrl.startsWith(endpoint)
) {
try {
const newUrl = await uploadToS3FromUrl(
avatarUrl,
`avatars/${model.id}/${uuid.v4()}`,
'public-read'
"public-read"
);
if (newUrl) model.avatarUrl = newUrl;
} catch (err) {
@@ -131,10 +131,10 @@ Team.prototype.provisionSubdomain = async function(subdomain) {
Team.prototype.provisionFirstCollection = async function(userId) {
const collection = await Collection.create({
name: 'Welcome',
name: "Welcome",
description:
'This collection is a quick guide to what Outline is all about. Feel free to delete this collection once your team is up to speed with the basics!',
type: 'atlas',
"This collection is a quick guide to what Outline is all about. Feel free to delete this collection once your team is up to speed with the basics!",
type: "atlas",
teamId: this.id,
creatorId: userId,
});
@@ -142,15 +142,15 @@ Team.prototype.provisionFirstCollection = async function(userId) {
// For the first collection we go ahead and create some intitial documents to get
// the team started. You can edit these in /server/onboarding/x.md
const onboardingDocs = [
'❤️ Support',
'🚀 Integrations & API',
'📝 Our Editor',
'👋 What is Outline',
"❤️ Support",
"🚀 Integrations & API",
"📝 Our Editor",
"👋 What is Outline",
];
for (const title of onboardingDocs) {
const text = await readFile(
path.join(__dirname, '..', 'onboarding', `${title}.md`),
'utf8'
path.join(__dirname, "..", "onboarding", `${title}.md`),
"utf8"
);
const document = await Document.create({
version: 1,
@@ -186,13 +186,13 @@ Team.prototype.removeAdmin = async function(user: User) {
if (res.count >= 1) {
return user.update({ isAdmin: false });
} else {
throw new ValidationError('At least one admin is required');
throw new ValidationError("At least one admin is required");
}
};
Team.prototype.suspendUser = async function(user: User, admin: User) {
if (user.id === admin.id)
throw new ValidationError('Unable to suspend the current user');
throw new ValidationError("Unable to suspend the current user");
return user.update({
suspendedById: admin.id,
suspendedAt: new Date(),
@@ -208,7 +208,7 @@ Team.prototype.activateUser = async function(user: User, admin: User) {
Team.prototype.collectionIds = async function(paranoid: boolean = true) {
let models = await Collection.findAll({
attributes: ['id', 'private'],
attributes: ["id", "private"],
where: { teamId: this.id, private: false },
paranoid,
});

View File

@@ -1,28 +1,28 @@
/* eslint-disable flowtype/require-valid-file-annotation */
import { flushdb } from '../test/support';
import { buildTeam } from '../test/factories';
import { flushdb } from "../test/support";
import { buildTeam } from "../test/factories";
beforeEach(flushdb);
it('should set subdomain if available', async () => {
it("should set subdomain if available", async () => {
const team = await buildTeam();
const subdomain = await team.provisionSubdomain('testy');
expect(subdomain).toEqual('testy');
expect(team.subdomain).toEqual('testy');
const subdomain = await team.provisionSubdomain("testy");
expect(subdomain).toEqual("testy");
expect(team.subdomain).toEqual("testy");
});
it('should set subdomain with append if unavailable', async () => {
await buildTeam({ subdomain: 'myteam' });
it("should set subdomain with append if unavailable", async () => {
await buildTeam({ subdomain: "myteam" });
const team = await buildTeam();
const subdomain = await team.provisionSubdomain('myteam');
expect(subdomain).toEqual('myteam1');
expect(team.subdomain).toEqual('myteam1');
const subdomain = await team.provisionSubdomain("myteam");
expect(subdomain).toEqual("myteam1");
expect(team.subdomain).toEqual("myteam1");
});
it('should do nothing if subdomain already set', async () => {
const team = await buildTeam({ subdomain: 'example' });
const subdomain = await team.provisionSubdomain('myteam');
expect(subdomain).toEqual('example');
expect(team.subdomain).toEqual('example');
it("should do nothing if subdomain already set", async () => {
const team = await buildTeam({ subdomain: "example" });
const subdomain = await team.provisionSubdomain("myteam");
expect(subdomain).toEqual("example");
expect(team.subdomain).toEqual("example");
});

View File

@@ -1,18 +1,18 @@
// @flow
import crypto from 'crypto';
import uuid from 'uuid';
import JWT from 'jsonwebtoken';
import subMinutes from 'date-fns/sub_minutes';
import { ValidationError } from '../errors';
import { DataTypes, sequelize, encryptedFields } from '../sequelize';
import { publicS3Endpoint, uploadToS3FromUrl } from '../utils/s3';
import { sendEmail } from '../mailer';
import { Star, Team, Collection, NotificationSetting, ApiKey } from '.';
import crypto from "crypto";
import uuid from "uuid";
import JWT from "jsonwebtoken";
import subMinutes from "date-fns/sub_minutes";
import { ValidationError } from "../errors";
import { DataTypes, sequelize, encryptedFields } from "../sequelize";
import { publicS3Endpoint, uploadToS3FromUrl } from "../utils/s3";
import { sendEmail } from "../mailer";
import { Star, Team, Collection, NotificationSetting, ApiKey } from ".";
const DEFAULT_AVATAR_HOST = 'https://tiley.herokuapp.com';
const DEFAULT_AVATAR_HOST = "https://tiley.herokuapp.com";
const User = sequelize.define(
'user',
"user",
{
id: {
type: DataTypes.UUID,
@@ -27,7 +27,7 @@ const User = sequelize.define(
service: { type: DataTypes.STRING, allowNull: true },
serviceId: { type: DataTypes.STRING, allowNull: true, unique: true },
slackData: DataTypes.JSONB,
jwtSecret: encryptedFields.vault('jwtSecret'),
jwtSecret: encryptedFields.vault("jwtSecret"),
lastActiveAt: DataTypes.DATE,
lastActiveIp: { type: DataTypes.STRING, allowNull: true },
lastSignedInAt: DataTypes.DATE,
@@ -43,15 +43,15 @@ const User = sequelize.define(
return !!this.suspendedAt;
},
avatarUrl() {
const original = this.getDataValue('avatarUrl');
const original = this.getDataValue("avatarUrl");
if (original) {
return original;
}
const hash = crypto
.createHash('md5')
.update(this.email || '')
.digest('hex');
.createHash("md5")
.update(this.email || "")
.digest("hex");
return `${DEFAULT_AVATAR_HOST}/avatar/${hash}/${this.name[0]}.png`;
},
},
@@ -60,22 +60,22 @@ const User = sequelize.define(
// Class methods
User.associate = models => {
User.hasMany(models.ApiKey, { as: 'apiKeys', onDelete: 'cascade' });
User.hasMany(models.ApiKey, { as: "apiKeys", onDelete: "cascade" });
User.hasMany(models.NotificationSetting, {
as: 'notificationSettings',
onDelete: 'cascade',
as: "notificationSettings",
onDelete: "cascade",
});
User.hasMany(models.Document, { as: 'documents' });
User.hasMany(models.View, { as: 'views' });
User.hasMany(models.Document, { as: "documents" });
User.hasMany(models.View, { as: "views" });
User.belongsTo(models.Team);
};
// Instance methods
User.prototype.collectionIds = async function(paranoid: boolean = true) {
const collectionStubs = await Collection.scope({
method: ['withMembership', this.id],
method: ["withMembership", this.id],
}).findAll({
attributes: ['id', 'private'],
attributes: ["id", "private"],
where: { teamId: this.teamId },
paranoid,
});
@@ -113,8 +113,8 @@ User.prototype.getJwtToken = function() {
};
User.prototype.getEmailSigninToken = function() {
if (this.service && this.service !== 'email') {
throw new Error('Cannot generate email signin token for OAuth user');
if (this.service && this.service !== "email") {
throw new Error("Cannot generate email signin token for OAuth user");
}
return JWT.sign(
@@ -129,7 +129,7 @@ const uploadAvatar = async model => {
if (
avatarUrl &&
!avatarUrl.startsWith('/api') &&
!avatarUrl.startsWith("/api") &&
!avatarUrl.startsWith(endpoint) &&
!avatarUrl.startsWith(DEFAULT_AVATAR_HOST)
) {
@@ -137,7 +137,7 @@ const uploadAvatar = async model => {
const newUrl = await uploadToS3FromUrl(
avatarUrl,
`avatars/${model.id}/${uuid.v4()}`,
'public-read'
"public-read"
);
if (newUrl) model.avatarUrl = newUrl;
} catch (err) {
@@ -148,7 +148,7 @@ const uploadAvatar = async model => {
};
const setRandomJwtSecret = model => {
model.jwtSecret = crypto.randomBytes(64).toString('hex');
model.jwtSecret = crypto.randomBytes(64).toString("hex");
};
const removeIdentifyingInfo = async (model, options) => {
@@ -166,8 +166,8 @@ const removeIdentifyingInfo = async (model, options) => {
});
model.email = null;
model.name = 'Unknown';
model.avatarUrl = '';
model.name = "Unknown";
model.avatarUrl = "";
model.serviceId = null;
model.username = null;
model.slackData = null;
@@ -188,7 +188,7 @@ const checkLastAdmin = async model => {
if (userCount > 1 && adminCount <= 1) {
throw new ValidationError(
'Cannot delete account as only admin. Please transfer admin permissions to another user and try again.'
"Cannot delete account as only admin. Please transfer admin permissions to another user and try again."
);
}
}
@@ -204,8 +204,8 @@ User.afterCreate(async user => {
// From Slack support:
// If you wish to contact users at an email address obtained through Slack,
// you need them to opt-in through a clear and separate process.
if (user.service && user.service !== 'slack') {
sendEmail('welcome', user.email, { teamUrl: team.url });
if (user.service && user.service !== "slack") {
sendEmail("welcome", user.email, { teamUrl: team.url });
}
});
@@ -217,7 +217,7 @@ User.afterCreate(async (user, options) => {
where: {
userId: user.id,
teamId: user.teamId,
event: 'documents.update',
event: "documents.update",
},
transaction: options.transaction,
}),
@@ -225,7 +225,7 @@ User.afterCreate(async (user, options) => {
where: {
userId: user.id,
teamId: user.teamId,
event: 'emails.onboarding',
event: "emails.onboarding",
},
transaction: options.transaction,
}),

View File

@@ -1,10 +1,10 @@
/* eslint-disable flowtype/require-valid-file-annotation */
import { flushdb } from '../test/support';
import { buildUser } from '../test/factories';
import { flushdb } from "../test/support";
import { buildUser } from "../test/factories";
beforeEach(flushdb);
it('should set JWT secret', async () => {
it("should set JWT secret", async () => {
const user = await buildUser();
expect(user.getJwtToken()).toBeTruthy();
});

View File

@@ -1,11 +1,11 @@
// @flow
import subMilliseconds from 'date-fns/sub_milliseconds';
import { Op, DataTypes, sequelize } from '../sequelize';
import { User } from '../models';
import { USER_PRESENCE_INTERVAL } from '../../shared/constants';
import subMilliseconds from "date-fns/sub_milliseconds";
import { Op, DataTypes, sequelize } from "../sequelize";
import { User } from "../models";
import { USER_PRESENCE_INTERVAL } from "../../shared/constants";
const View = sequelize.define(
'view',
"view",
{
id: {
type: DataTypes.UUID,
@@ -42,7 +42,7 @@ View.increment = async where => {
View.findByDocument = async documentId => {
return View.findAll({
where: { documentId },
order: [['updatedAt', 'DESC']],
order: [["updatedAt", "DESC"]],
include: [
{
model: User,
@@ -60,7 +60,7 @@ View.findRecentlyEditingByDocument = async documentId => {
[Op.gt]: subMilliseconds(new Date(), USER_PRESENCE_INTERVAL * 2),
},
},
order: [['lastEditingAt', 'DESC']],
order: [["lastEditingAt", "DESC"]],
});
};

View File

@@ -1,24 +1,24 @@
// @flow
import ApiKey from './ApiKey';
import Attachment from './Attachment';
import Authentication from './Authentication';
import Backlink from './Backlink';
import Collection from './Collection';
import CollectionUser from './CollectionUser';
import CollectionGroup from './CollectionGroup';
import Document from './Document';
import Event from './Event';
import Integration from './Integration';
import Group from './Group';
import GroupUser from './GroupUser';
import Notification from './Notification';
import NotificationSetting from './NotificationSetting';
import Revision from './Revision';
import Share from './Share';
import Star from './Star';
import Team from './Team';
import User from './User';
import View from './View';
import ApiKey from "./ApiKey";
import Attachment from "./Attachment";
import Authentication from "./Authentication";
import Backlink from "./Backlink";
import Collection from "./Collection";
import CollectionUser from "./CollectionUser";
import CollectionGroup from "./CollectionGroup";
import Document from "./Document";
import Event from "./Event";
import Integration from "./Integration";
import Group from "./Group";
import GroupUser from "./GroupUser";
import Notification from "./Notification";
import NotificationSetting from "./NotificationSetting";
import Revision from "./Revision";
import Share from "./Share";
import Star from "./Star";
import Team from "./Team";
import User from "./User";
import View from "./View";
const models = {
ApiKey,
@@ -45,7 +45,7 @@ const models = {
// based on https://github.com/sequelize/express-example/blob/master/models/index.js
Object.keys(models).forEach(modelName => {
if ('associate' in models[modelName]) {
if ("associate" in models[modelName]) {
models[modelName].associate(models);
}
});