chore: Typescript database models (#2886)

closes #2798
This commit is contained in:
Tom Moor
2022-01-06 18:24:28 -08:00
committed by GitHub
parent d3cbf250e6
commit b20a341f0c
207 changed files with 5624 additions and 5315 deletions

View File

@@ -1,30 +1,19 @@
import { Collection, UserAuthentication } from "@server/models";
import mailer from "@server/mailer";
import Collection from "@server/models/Collection";
import UserAuthentication from "@server/models/UserAuthentication";
import { buildUser, buildTeam } from "@server/test/factories";
import { flushdb } from "@server/test/support";
import mailer from "../mailer";
import accountProvisioner from "./accountProvisioner";
jest.mock("../mailer");
jest.mock("aws-sdk", () => {
const mS3 = {
createPresignedPost: jest.fn(),
putObject: jest.fn().mockReturnThis(),
promise: jest.fn(),
};
return {
S3: jest.fn(() => mS3),
Endpoint: jest.fn(),
};
});
beforeEach(() => {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'mockReset' does not exist on type '(type... Remove this comment to see the full error message
mailer.sendTemplate.mockReset();
return flushdb();
});
describe("accountProvisioner", () => {
const ip = "127.0.0.1";
it("should create a new user and team", async () => {
const spy = jest.spyOn(mailer, "sendTemplate");
const { user, team, isNewTeam, isNewUser } = await accountProvisioner({
ip,
user: {
@@ -48,7 +37,7 @@ describe("accountProvisioner", () => {
scopes: ["read"],
},
});
const authentications = await user.getAuthentications();
const authentications = await user.$get("authentications");
const auth = authentications[0];
expect(auth.accessToken).toEqual("123");
expect(auth.scopes.length).toEqual(1);
@@ -58,19 +47,22 @@ describe("accountProvisioner", () => {
expect(user.username).toEqual("jtester");
expect(isNewUser).toEqual(true);
expect(isNewTeam).toEqual(true);
expect(mailer.sendTemplate).toHaveBeenCalled();
expect(spy).toHaveBeenCalled();
const collectionCount = await Collection.count();
expect(collectionCount).toEqual(1);
spy.mockRestore();
});
it("should update exising user and authentication", async () => {
const spy = jest.spyOn(mailer, "sendTemplate");
const existingTeam = await buildTeam();
const providers = await existingTeam.getAuthenticationProviders();
const providers = await existingTeam.$get("authenticationProviders");
const authenticationProvider = providers[0];
const existing = await buildUser({
teamId: existingTeam.id,
});
const authentications = await existing.getAuthentications();
const authentications = await existing.$get("authentications");
const authentication = authentications[0];
const newEmail = "test@example.com";
const newUsername = "tname";
@@ -98,21 +90,23 @@ describe("accountProvisioner", () => {
},
});
const auth = await UserAuthentication.findByPk(authentication.id);
expect(auth.accessToken).toEqual("123");
expect(auth.scopes.length).toEqual(1);
expect(auth.scopes[0]).toEqual("read");
expect(auth?.accessToken).toEqual("123");
expect(auth?.scopes.length).toEqual(1);
expect(auth?.scopes[0]).toEqual("read");
expect(user.email).toEqual(newEmail);
expect(user.username).toEqual(newUsername);
expect(isNewTeam).toEqual(false);
expect(isNewUser).toEqual(false);
expect(mailer.sendTemplate).not.toHaveBeenCalled();
expect(spy).not.toHaveBeenCalled();
const collectionCount = await Collection.count();
expect(collectionCount).toEqual(0);
spy.mockRestore();
});
it("should throw an error when authentication provider is disabled", async () => {
const existingTeam = await buildTeam();
const providers = await existingTeam.getAuthenticationProviders();
const providers = await existingTeam.$get("authenticationProviders");
const authenticationProvider = providers[0];
await authenticationProvider.update({
enabled: false,
@@ -120,7 +114,7 @@ describe("accountProvisioner", () => {
const existing = await buildUser({
teamId: existingTeam.id,
});
const authentications = await existing.getAuthentications();
const authentications = await existing.$get("authentications");
const authentication = authentications[0];
let error;
@@ -129,7 +123,7 @@ describe("accountProvisioner", () => {
ip,
user: {
name: existing.name,
email: existing.email,
email: existing.email!,
avatarUrl: existing.avatarUrl,
},
team: {
@@ -155,8 +149,9 @@ describe("accountProvisioner", () => {
});
it("should create a new user in an existing team", async () => {
const spy = jest.spyOn(mailer, "sendTemplate");
const team = await buildTeam();
const authenticationProviders = await team.getAuthenticationProviders();
const authenticationProviders = await team.$get("authenticationProviders");
const authenticationProvider = authenticationProviders[0];
const { user, isNewUser } = await accountProvisioner({
ip,
@@ -181,7 +176,7 @@ describe("accountProvisioner", () => {
scopes: ["read"],
},
});
const authentications = await user.getAuthentications();
const authentications = await user.$get("authentications");
const auth = authentications[0];
expect(auth.accessToken).toEqual("123");
expect(auth.scopes.length).toEqual(1);
@@ -189,9 +184,11 @@ describe("accountProvisioner", () => {
expect(user.email).toEqual("jenny@example.com");
expect(user.username).toEqual("jtester");
expect(isNewUser).toEqual(true);
expect(mailer.sendTemplate).toHaveBeenCalled();
expect(spy).toHaveBeenCalled();
// should provision welcome collection
const collectionCount = await Collection.count();
expect(collectionCount).toEqual(1);
spy.mockRestore();
});
});

View File

@@ -1,12 +1,12 @@
import invariant from "invariant";
import Sequelize from "sequelize";
import { Collection, Team, User } from "@server/models";
import { UniqueConstraintError } from "sequelize";
import {
AuthenticationError,
EmailAuthenticationRequiredError,
AuthenticationProviderDisabledError,
} from "../errors";
import mailer from "../mailer";
} from "@server/errors";
import mailer from "@server/mailer";
import { Collection, Team, User } from "@server/models";
import teamCreator from "./teamCreator";
import userCreator from "./userCreator";
@@ -15,14 +15,14 @@ type Props = {
user: {
name: string;
email: string;
avatarUrl?: string;
avatarUrl?: string | null;
username?: string;
};
team: {
name: string;
domain?: string;
subdomain: string;
avatarUrl?: string;
avatarUrl?: string | null;
};
authenticationProvider: {
name: string;
@@ -37,9 +37,7 @@ type Props = {
};
export type AccountProvisionerResult = {
// @ts-expect-error ts-migrate(2749) FIXME: 'User' refers to a value, but is being used as a t... Remove this comment to see the full error message
user: User;
// @ts-expect-error ts-migrate(2749) FIXME: 'Team' refers to a value, but is being used as a t... Remove this comment to see the full error message
team: Team;
isNewTeam: boolean;
isNewUser: boolean;
@@ -123,7 +121,7 @@ export default async function accountProvisioner({
isNewTeam,
};
} catch (err) {
if (err instanceof Sequelize.UniqueConstraintError) {
if (err instanceof UniqueConstraintError) {
const exists = await User.findOne({
where: {
email: userParams.email,

View File

@@ -13,7 +13,6 @@ export default async function attachmentCreator({
name: string;
type: string;
buffer: Buffer;
// @ts-expect-error ts-migrate(2749) FIXME: 'User' refers to a value, but is being used as a t... Remove this comment to see the full error message
user: User;
source?: "import";
ip: string;

View File

@@ -7,11 +7,8 @@ export default async function collectionExporter({
user,
ip,
}: {
// @ts-expect-error ts-migrate(2749) FIXME: 'Collection' refers to a value, but is being used ... Remove this comment to see the full error message
collection?: Collection;
// @ts-expect-error ts-migrate(2749) FIXME: 'Team' refers to a value, but is being used as a t... Remove this comment to see the full error message
team: Team;
// @ts-expect-error ts-migrate(2749) FIXME: 'User' refers to a value, but is being used as a t... Remove this comment to see the full error message
user: User;
ip: string;
}) {
@@ -39,6 +36,10 @@ export default async function collectionExporter({
});
fileOperation.user = user;
fileOperation.collection = collection;
if (collection) {
fileOperation.collection = collection;
}
return fileOperation;
}

View File

@@ -7,6 +7,7 @@ import collectionImporter from "./collectionImporter";
jest.mock("../utils/s3");
beforeEach(() => flushdb());
describe("collectionImporter", () => {
const ip = "127.0.0.1";

View File

@@ -24,7 +24,6 @@ export default async function collectionImporter({
ip,
}: {
file: FileWithPath;
// @ts-expect-error ts-migrate(2749) FIXME: 'User' refers to a value, but is being used as a t... Remove this comment to see the full error message
user: User;
type: "outline";
ip: string;
@@ -48,7 +47,6 @@ export default async function collectionImporter({
// store progress and pointers
// @ts-expect-error ts-migrate(2741) FIXME: Property 'string' is missing in type '{}' but requ... Remove this comment to see the full error message
const collections: {
// @ts-expect-error ts-migrate(2749) FIXME: 'Collection' refers to a value, but is being used ... Remove this comment to see the full error message
string: Collection;
} = {};
// @ts-expect-error ts-migrate(2741) FIXME: Property 'string' is missing in type '{}' but requ... Remove this comment to see the full error message
@@ -57,7 +55,6 @@ export default async function collectionImporter({
} = {};
// @ts-expect-error ts-migrate(2741) FIXME: Property 'string' is missing in type '{}' but requ... Remove this comment to see the full error message
const attachments: {
// @ts-expect-error ts-migrate(2749) FIXME: 'Attachment' refers to a value, but is being used ... Remove this comment to see the full error message
string: Attachment;
} = {};
@@ -186,13 +183,13 @@ export default async function collectionImporter({
/(.*)uploads\//,
"uploads/"
);
// @ts-expect-error ts-migrate(2339) FIXME: Property 'text' does not exist on type 'Document'.
document.text = document.text
.replace(attachmentPath, attachment.redirectUrl)
.replace(normalizedAttachmentPath, attachment.redirectUrl)
.replace(`/${normalizedAttachmentPath}`, attachment.redirectUrl);
// does nothing if the document text is unchanged
// @ts-expect-error ts-migrate(2339) FIXME: Property 'save' does not exist on type 'Document'.
await document.save({
fields: ["text"],
});

View File

@@ -1,3 +1,4 @@
import invariant from "invariant";
import { Document, Event, User } from "@server/models";
export default async function documentCreator({
@@ -21,18 +22,16 @@ export default async function documentCreator({
publish?: boolean;
collectionId: string;
parentDocumentId?: string;
templateDocument?: Document;
templateDocument?: Document | null;
template?: boolean;
createdAt?: Date;
updatedAt?: Date;
index?: number;
// @ts-expect-error ts-migrate(2749) FIXME: 'User' refers to a value, but is being used as a t... Remove this comment to see the full error message
user: User;
editorVersion?: string;
source?: "import";
ip: string;
}): Promise<Document> {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'id' does not exist on type 'Document'.
const templateId = templateDocument ? templateDocument.id : undefined;
const document = await Document.create({
parentDocumentId,
@@ -47,7 +46,6 @@ export default async function documentCreator({
template,
templateId,
title: templateDocument ? templateDocument.title : title,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'text' does not exist on type 'Document'.
text: templateDocument ? templateDocument.text : text,
});
await Event.create({
@@ -83,10 +81,13 @@ export default async function documentCreator({
// reload to get all of the data needed to present (user, collection etc)
// we need to specify publishedAt to bypass default scope that only returns
// published documents
return Document.findOne({
const doc = await Document.findOne({
where: {
id: document.id,
publishedAt: document.publishedAt,
},
});
invariant(doc, "Document must exist");
return doc;
}

View File

@@ -1,12 +1,13 @@
import path from "path";
import File from "formidable/lib/file";
import { Attachment } from "@server/models";
import Attachment from "@server/models/Attachment";
import { buildUser } from "@server/test/factories";
import { flushdb } from "@server/test/support";
import documentImporter from "./documentImporter";
jest.mock("../utils/s3");
beforeEach(() => flushdb());
describe("documentImporter", () => {
const ip = "127.0.0.1";

View File

@@ -144,7 +144,6 @@ export default async function documentImporter({
user,
ip,
}: {
// @ts-expect-error ts-migrate(2749) FIXME: 'User' refers to a value, but is being used as a t... Remove this comment to see the full error message
user: User;
file: File;
ip: string;

View File

@@ -1,4 +1,4 @@
import { Attachment } from "@server/models";
import Attachment from "@server/models/Attachment";
import {
buildDocument,
buildAttachment,
@@ -10,6 +10,7 @@ import parseAttachmentIds from "@server/utils/parseAttachmentIds";
import documentMover from "./documentMover";
beforeEach(() => flushdb());
describe("documentMover", () => {
const ip = "127.0.0.1";
@@ -33,7 +34,7 @@ describe("documentMover", () => {
const document = await buildDocument({
collectionId: collection.id,
});
await document.archive();
await document.archive(user.id);
const response = await documentMover({
user,
document,
@@ -63,13 +64,11 @@ describe("documentMover", () => {
index: 0,
ip,
});
// @ts-expect-error ts-migrate(2339) FIXME: Property 'documentStructure' does not exist on typ... Remove this comment to see the full error message
expect(response.collections[0].documentStructure[0].children[0].id).toBe(
expect(response.collections[0].documentStructure![0].children[0].id).toBe(
newDocument.id
);
expect(response.collections.length).toEqual(1);
expect(response.documents.length).toEqual(1);
// @ts-expect-error ts-migrate(2339) FIXME: Property 'collection' does not exist on type 'neve... Remove this comment to see the full error message
expect(response.documents[0].collection.id).toEqual(collection.id);
});
@@ -98,22 +97,17 @@ describe("documentMover", () => {
// check document ids where updated
await newDocument.reload();
expect(newDocument.collectionId).toBe(newCollection.id);
await document.reload();
expect(document.collectionId).toBe(newCollection.id);
// check collection structure updated
// @ts-expect-error ts-migrate(2339) FIXME: Property 'id' does not exist on type 'never'.
expect(response.collections[0].id).toBe(collection.id);
// @ts-expect-error ts-migrate(2339) FIXME: Property 'id' does not exist on type 'never'.
expect(response.collections[1].id).toBe(newCollection.id);
// @ts-expect-error ts-migrate(2339) FIXME: Property 'documentStructure' does not exist on typ... Remove this comment to see the full error message
expect(response.collections[1].documentStructure[0].children[0].id).toBe(
expect(response.collections[1].documentStructure![0].children[0].id).toBe(
newDocument.id
);
expect(response.collections.length).toEqual(2);
expect(response.documents.length).toEqual(2);
// @ts-expect-error ts-migrate(2339) FIXME: Property 'collection' does not exist on type 'neve... Remove this comment to see the full error message
expect(response.documents[0].collection.id).toEqual(newCollection.id);
// @ts-expect-error ts-migrate(2339) FIXME: Property 'collection' does not exist on type 'neve... Remove this comment to see the full error message
expect(response.documents[1].collection.id).toEqual(newCollection.id);
});
@@ -152,8 +146,8 @@ describe("documentMover", () => {
// check new attachment was created pointint to same key
const attachmentIds = parseAttachmentIds(newDocument.text);
const newAttachment = await Attachment.findByPk(attachmentIds[0]);
expect(newAttachment.documentId).toBe(newDocument.id);
expect(newAttachment.key).toBe(attachment.key);
expect(newAttachment?.documentId).toBe(newDocument.id);
expect(newAttachment?.key).toBe(attachment.key);
await document.reload();
expect(document.collectionId).toBe(newCollection.id);
});

View File

@@ -1,16 +1,22 @@
import invariant from "invariant";
import { Transaction } from "sequelize";
import { Document, Attachment, Collection, Pin, Event } from "@server/models";
import { sequelize } from "@server/database/sequelize";
import {
User,
Document,
Attachment,
Collection,
Pin,
Event,
} from "@server/models";
import parseAttachmentIds from "@server/utils/parseAttachmentIds";
import { sequelize } from "../sequelize";
import pinDestroyer from "./pinDestroyer";
async function copyAttachments(
document: Document,
options?: { transaction?: Transaction }
) {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'text' does not exist on type 'Document'.
let text = document.text;
// @ts-expect-error ts-migrate(2339) FIXME: Property 'id' does not exist on type 'Document'.
const documentId = document.id;
// find any image attachments that are in this documents text
const attachmentIds = parseAttachmentIds(text);
@@ -18,7 +24,6 @@ async function copyAttachments(
for (const id of attachmentIds) {
const existing = await Attachment.findOne({
where: {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'teamId' does not exist on type 'Document... Remove this comment to see the full error message
teamId: document.teamId,
id,
},
@@ -29,6 +34,7 @@ async function copyAttachments(
// then create a new attachment pointed to this doc and update the reference
// in the text so that it gets the moved documents permissions
if (existing && existing.documentId !== documentId) {
// @ts-expect-error dataValues exists
const { id, ...rest } = existing.dataValues;
const attachment = await Attachment.create(
{ ...rest, documentId },
@@ -41,6 +47,21 @@ async function copyAttachments(
return text;
}
type Props = {
user: User;
document: Document;
collectionId: string;
parentDocumentId?: string | null;
index?: number;
ip: string;
};
type Result = {
collections: Collection[];
documents: Document[];
collectionChanged: boolean;
};
export default async function documentMover({
user,
document,
@@ -49,19 +70,11 @@ export default async function documentMover({
// convert undefined to null so parentId comparison treats them as equal
index,
ip,
}: {
// @ts-expect-error ts-migrate(2749) FIXME: 'User' refers to a value, but is being used as a t... Remove this comment to see the full error message
user: User;
document: any;
collectionId: string;
parentDocumentId?: string | null;
index?: number;
ip: string;
}) {
}: Props): Promise<Result> {
let transaction: Transaction | undefined;
const collectionChanged = collectionId !== document.collectionId;
const previousCollectionId = document.collectionId;
const result = {
const result: Result = {
collections: [],
documents: [],
collectionChanged,
@@ -77,7 +90,6 @@ export default async function documentMover({
document.lastModifiedById = user.id;
document.updatedBy = user;
await document.save();
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'Document' is not assignable to p... Remove this comment to see the full error message
result.documents.push(document);
} else {
try {
@@ -88,12 +100,13 @@ export default async function documentMover({
transaction,
paranoid: false,
});
const [
documentJson,
fromIndex,
] = (await collection.removeDocumentInStructure(document, {
const response = await collection?.removeDocumentInStructure(document, {
save: false,
})) || [undefined, index];
});
const documentJson = response?.[0];
const fromIndex = response?.[1] || 0;
// if we're reordering from within the same parent
// the original and destination collection are the same,
@@ -110,7 +123,7 @@ export default async function documentMover({
// if the collection is the same then it will get saved below, this
// line prevents a pointless intermediate save from occurring.
if (collectionChanged) {
await collection.save({
await collection?.save({
transaction,
});
document.text = await copyAttachments(document, {
@@ -124,36 +137,37 @@ export default async function documentMover({
document.lastModifiedById = user.id;
document.updatedBy = user;
// @ts-expect-error ts-migrate(2749) FIXME: 'Collection' refers to a value, but is being used ... Remove this comment to see the full error message
const newCollection: Collection = collectionChanged
const newCollection = collectionChanged
? await Collection.scope({
method: ["withMembership", user.id],
}).findByPk(collectionId, {
transaction,
})
: collection;
await newCollection.addDocumentToStructure(document, toIndex, {
invariant(newCollection, "collection should exist");
await newCollection?.addDocumentToStructure(document, toIndex, {
documentJson,
});
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'any' is not assignable to parame... Remove this comment to see the full error message
result.collections.push(collection);
if (collection) {
result.collections.push(collection);
}
// if collection does not remain the same loop through children and change their
// collectionId and move any attachments they may have too. This includes
// archived children, otherwise their collection would be wrong once restored.
if (collectionChanged) {
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'any' is not assignable to parame... Remove this comment to see the full error message
result.collections.push(newCollection);
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'documentId' implicitly has an 'any' typ... Remove this comment to see the full error message
const loopChildren = async (documentId) => {
const loopChildren = async (documentId: string) => {
const childDocuments = await Document.findAll({
where: {
parentDocumentId: documentId,
},
});
await Promise.all(
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'child' implicitly has an 'any' type.
childDocuments.map(async (child) => {
await loopChildren(child.id);
child.text = await copyAttachments(child, {
@@ -161,8 +175,10 @@ export default async function documentMover({
});
child.collectionId = collectionId;
await child.save();
child.collection = newCollection;
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'any' is not assignable to parame... Remove this comment to see the full error message
if (newCollection) {
child.collection = newCollection;
}
result.documents.push(child);
})
);
@@ -189,13 +205,13 @@ export default async function documentMover({
await document.save({
transaction,
});
document.collection = newCollection;
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'Document' is not assignable to p... Remove this comment to see the full error message
if (newCollection) {
document.collection = newCollection;
}
result.documents.push(document);
if (transaction) {
await transaction.commit();
}
await transaction.commit();
} catch (err) {
if (transaction) {
await transaction.rollback();
@@ -213,9 +229,7 @@ export default async function documentMover({
teamId: document.teamId,
data: {
title: document.title,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'id' does not exist on type 'never'.
collectionIds: result.collections.map((c) => c.id),
// @ts-expect-error ts-migrate(2339) FIXME: Property 'id' does not exist on type 'never'.
documentIds: result.documents.map((d) => d.id),
},
ip,

View File

@@ -4,18 +4,8 @@ import { buildAttachment, buildDocument } from "@server/test/factories";
import { flushdb } from "@server/test/support";
import documentPermanentDeleter from "./documentPermanentDeleter";
jest.mock("aws-sdk", () => {
const mS3 = {
createPresignedPost: jest.fn(),
deleteObject: jest.fn().mockReturnThis(),
promise: jest.fn(),
};
return {
S3: jest.fn(() => mS3),
Endpoint: jest.fn(),
};
});
beforeEach(() => flushdb());
describe("documentPermanentDeleter", () => {
it("should destroy documents", async () => {
const document = await buildDocument({

View File

@@ -1,15 +1,14 @@
import { QueryTypes } from "sequelize";
import { sequelize } from "@server/database/sequelize";
import Logger from "@server/logging/logger";
import { Document, Attachment } from "@server/models";
import parseAttachmentIds from "@server/utils/parseAttachmentIds";
import { sequelize } from "../sequelize";
export default async function documentPermanentDeleter(documents: Document[]) {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'deletedAt' does not exist on type 'Docum... Remove this comment to see the full error message
const activeDocument = documents.find((doc) => !doc.deletedAt);
if (activeDocument) {
throw new Error(
// @ts-expect-error ts-migrate(2339) FIXME: Property 'id' does not exist on type 'Document'.
`Cannot permanently delete ${activeDocument.id} document. Please delete it and try again.`
);
}
@@ -23,16 +22,13 @@ export default async function documentPermanentDeleter(documents: Document[]) {
`;
for (const document of documents) {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'text' does not exist on type 'Document'.
const attachmentIds = parseAttachmentIds(document.text);
for (const attachmentId of attachmentIds) {
const [{ count }] = await sequelize.query(query, {
type: sequelize.QueryTypes.SELECT,
type: QueryTypes.SELECT,
replacements: {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'id' does not exist on type 'Document'.
documentId: document.id,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'teamId' does not exist on type 'Document... Remove this comment to see the full error message
teamId: document.teamId,
query: attachmentId,
},
@@ -41,7 +37,6 @@ export default async function documentPermanentDeleter(documents: Document[]) {
if (parseInt(count) === 0) {
const attachment = await Attachment.findOne({
where: {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'teamId' does not exist on type 'Document... Remove this comment to see the full error message
teamId: document.teamId,
id: attachmentId,
},
@@ -59,7 +54,6 @@ export default async function documentPermanentDeleter(documents: Document[]) {
return Document.scope("withUnpublished").destroy({
where: {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'id' does not exist on type 'Document'.
id: documents.map((document) => document.id),
},
force: true,

View File

@@ -1,3 +1,4 @@
import invariant from "invariant";
import { uniq } from "lodash";
import { Node } from "prosemirror-model";
import { schema, serializer } from "rich-markdown-editor";
@@ -15,6 +16,8 @@ export default async function documentUpdater({
userId?: string;
}) {
const document = await Document.findByPk(documentId);
invariant(document, "document not found");
const state = Y.encodeStateAsUpdate(ydoc);
const node = Node.fromJSON(schema, yDocToProsemirrorJSON(ydoc, "default"));
const text = serializer.serialize(node, undefined);
@@ -30,6 +33,7 @@ export default async function documentUpdater({
const pudIds = Array.from(pud.clients.values());
const existingIds = document.collaboratorIds;
const collaboratorIds = uniq([...pudIds, ...existingIds]);
await Document.scope("withUnpublished").update(
{
text,

View File

@@ -3,18 +3,8 @@ import { buildAdmin, buildFileOperation } from "@server/test/factories";
import { flushdb } from "@server/test/support";
import fileOperationDeleter from "./fileOperationDeleter";
jest.mock("aws-sdk", () => {
const mS3 = {
createPresignedPost: jest.fn(),
deleteObject: jest.fn().mockReturnThis(),
promise: jest.fn(),
};
return {
S3: jest.fn(() => mS3),
Endpoint: jest.fn(),
};
});
beforeEach(() => flushdb());
describe("fileOperationDeleter", () => {
const ip = "127.0.0.1";

View File

@@ -1,10 +1,8 @@
import { sequelize } from "@server/database/sequelize";
import { FileOperation, Event, User } from "@server/models";
import { sequelize } from "../sequelize";
export default async function fileOperationDeleter(
// @ts-expect-error ts-migrate(2749) FIXME: 'FileOperation' refers to a value, but is being us... Remove this comment to see the full error message
fileOp: FileOperation,
// @ts-expect-error ts-migrate(2749) FIXME: 'User' refers to a value, but is being used as a t... Remove this comment to see the full error message
user: User,
ip: string
) {
@@ -19,6 +17,7 @@ export default async function fileOperationDeleter(
name: "fileOperations.delete",
teamId: user.teamId,
actorId: user.id,
// @ts-expect-error dataValues does exist
data: fileOp.dataValues,
ip,
},

View File

@@ -25,8 +25,8 @@ describe("pinCreator", () => {
expect(pin.collectionId).toEqual(null);
expect(pin.createdById).toEqual(user.id);
expect(pin.index).toEqual("P");
expect(event.name).toEqual("pins.create");
expect(event.modelId).toEqual(pin.id);
expect(event!.name).toEqual("pins.create");
expect(event!.modelId).toEqual(pin.id);
});
it("should create pin to collection", async () => {
@@ -48,8 +48,8 @@ describe("pinCreator", () => {
expect(pin.collectionId).toEqual(document.collectionId);
expect(pin.createdById).toEqual(user.id);
expect(pin.index).toEqual("P");
expect(event.name).toEqual("pins.create");
expect(event.modelId).toEqual(pin.id);
expect(event.collectionId).toEqual(pin.collectionId);
expect(event!.name).toEqual("pins.create");
expect(event!.modelId).toEqual(pin.id);
expect(event!.collectionId).toEqual(pin.collectionId);
});
});

View File

@@ -1,13 +1,14 @@
import fractionalIndex from "fractional-index";
import { Sequelize, Op, WhereOptions } from "sequelize";
import { sequelize } from "@server/database/sequelize";
import { ValidationError } from "@server/errors";
import { Pin, Event } from "@server/models";
import { sequelize, Op } from "@server/sequelize";
import { Pin, User, Event } from "@server/models";
const MAX_PINS = 8;
type Props = {
/** The user creating the pin */
user: any;
user: User;
/** The document to pin */
documentId: string;
/** The collection to pin the document in. If no collection is provided then it will be pinned to home */
@@ -31,11 +32,11 @@ export default async function pinCreator({
collectionId,
ip,
...rest
}: Props): Promise<any> {
}: Props): Promise<Pin> {
let { index } = rest;
const where = {
const where: WhereOptions<Pin> = {
teamId: user.teamId,
...(collectionId ? { collectionId } : { collectionId: { [Op.eq]: null } }),
...(collectionId ? { collectionId } : { collectionId: { [Op.is]: null } }),
};
const count = await Pin.count({ where });
@@ -51,7 +52,7 @@ export default async function pinCreator({
order: [
// using LC_COLLATE:"C" because we need byte order to drive the sorting
// find only the last pin so we can create an index after it
sequelize.literal('"pins"."index" collate "C" DESC'),
Sequelize.literal('"pin"."index" collate "C" DESC'),
["updatedAt", "ASC"],
],
});

View File

@@ -32,7 +32,7 @@ describe("pinCreator", () => {
expect(count).toEqual(0);
const event = await Event.findOne();
expect(event.name).toEqual("pins.delete");
expect(event.modelId).toEqual(pin.id);
expect(event!.name).toEqual("pins.delete");
expect(event!.modelId).toEqual(pin.id);
});
});

View File

@@ -1,12 +1,12 @@
import { Transaction } from "sequelize";
import { Event } from "@server/models";
import { sequelize } from "@server/sequelize";
import { sequelize } from "@server/database/sequelize";
import { Event, Pin, User } from "@server/models";
type Props = {
/** The user destroying the pin */
user: any;
user: User;
/** The pin to destroy */
pin: any;
pin: Pin;
/** The IP address of the user creating the pin */
ip: string;
/** Optional existing transaction */
@@ -25,7 +25,7 @@ export default async function pinDestroyer({
pin,
ip,
transaction: t,
}: Props): Promise<any> {
}: Props): Promise<Pin> {
const transaction = t || (await sequelize.transaction());
try {

View File

@@ -1,13 +1,13 @@
import { Event } from "@server/models";
import { sequelize } from "@server/sequelize";
import { sequelize } from "@server/database/sequelize";
import { Event, Pin, User } from "@server/models";
type Props = {
/** The user updating the pin */
user: any;
user: User;
/** The existing pin */
pin: any;
pin: Pin;
/** The index to pin the document at */
index?: string;
index: string;
/** The IP address of the user creating the pin */
ip: string;
};
@@ -24,7 +24,7 @@ export default async function pinUpdater({
pin,
index,
ip,
}: Props): Promise<any> {
}: Props): Promise<Pin> {
const transaction = await sequelize.transaction();
try {

View File

@@ -4,6 +4,7 @@ import { flushdb } from "@server/test/support";
import revisionCreator from "./revisionCreator";
beforeEach(() => flushdb());
describe("revisionCreator", () => {
const ip = "127.0.0.1";
@@ -21,8 +22,8 @@ describe("revisionCreator", () => {
const event = await Event.findOne();
expect(revision.documentId).toEqual(document.id);
expect(revision.userId).toEqual(user.id);
expect(event.name).toEqual("revisions.create");
expect(event.modelId).toEqual(revision.id);
expect(event.createdAt).toEqual(document.updatedAt);
expect(event!.name).toEqual("revisions.create");
expect(event!.modelId).toEqual(revision.id);
expect(event!.createdAt).toEqual(document.updatedAt);
});
});

View File

@@ -1,5 +1,5 @@
import { sequelize } from "@server/database/sequelize";
import { Document, User, Event, Revision } from "@server/models";
import { sequelize } from "../sequelize";
export default async function revisionCreator({
document,
@@ -7,7 +7,6 @@ export default async function revisionCreator({
ip,
}: {
document: Document;
// @ts-expect-error ts-migrate(2749) FIXME: 'User' refers to a value, but is being used as a t... Remove this comment to see the full error message
user: User;
ip?: string;
}) {
@@ -21,13 +20,10 @@ export default async function revisionCreator({
await Event.create(
{
name: "revisions.create",
// @ts-expect-error ts-migrate(2339) FIXME: Property 'id' does not exist on type 'Document'.
documentId: document.id,
modelId: revision.id,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'teamId' does not exist on type 'Document... Remove this comment to see the full error message
teamId: document.teamId,
actorId: user.id,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'updatedAt' does not exist on type 'Docum... Remove this comment to see the full error message
createdAt: document.updatedAt,
ip: ip || user.lastActiveIp,
},

View File

@@ -2,18 +2,8 @@ import { buildTeam } from "@server/test/factories";
import { flushdb } from "@server/test/support";
import teamCreator from "./teamCreator";
jest.mock("aws-sdk", () => {
const mS3 = {
createPresignedPost: jest.fn(),
putObject: jest.fn().mockReturnThis(),
promise: jest.fn(),
};
return {
S3: jest.fn(() => mS3),
Endpoint: jest.fn(),
};
});
beforeEach(() => flushdb());
describe("teamCreator", () => {
it("should create team and authentication provider", async () => {
const result = await teamCreator({
@@ -73,7 +63,7 @@ describe("teamCreator", () => {
expect(authenticationProvider.name).toEqual("google");
expect(authenticationProvider.providerId).toEqual("allowed-domain.com");
expect(isNewTeam).toEqual(false);
const providers = await team.getAuthenticationProviders();
const providers = await team.$get("authenticationProviders");
expect(providers.length).toEqual(2);
});

View File

@@ -1,34 +1,34 @@
import invariant from "invariant";
import Logger from "@server/logging/logger";
import { Team, AuthenticationProvider } from "@server/models";
import { getAllowedDomains } from "@server/utils/authentication";
import { generateAvatarUrl } from "@server/utils/avatars";
import { MaximumTeamsError } from "../errors";
import { sequelize } from "../sequelize";
type TeamCreatorResult = {
// @ts-expect-error ts-migrate(2749) FIXME: 'Team' refers to a value, but is being used as a t... Remove this comment to see the full error message
team: Team;
// @ts-expect-error ts-migrate(2749) FIXME: 'AuthenticationProvider' refers to a value, but is... Remove this comment to see the full error message
authenticationProvider: AuthenticationProvider;
isNewTeam: boolean;
};
type Props = {
name: string;
domain?: string;
subdomain: string;
avatarUrl?: string | null;
authenticationProvider: {
name: string;
providerId: string;
};
};
export default async function teamCreator({
name,
domain,
subdomain,
avatarUrl,
authenticationProvider,
}: {
name: string;
domain?: string;
subdomain: string;
avatarUrl?: string;
authenticationProvider: {
name: string;
providerId: string;
};
}): Promise<TeamCreatorResult> {
}: Props): Promise<TeamCreatorResult> {
let authP = await AuthenticationProvider.findOne({
where: authenticationProvider,
include: [
@@ -61,7 +61,12 @@ export default async function teamCreator({
// authentication provider to the existing team
if (teamCount === 1 && domain && getAllowedDomains().includes(domain)) {
const team = await Team.findOne();
authP = await team.createAuthenticationProvider(authenticationProvider);
invariant(team, "Team should exist");
authP = await team.$create<AuthenticationProvider>(
"authenticationProvider",
authenticationProvider
);
return {
authenticationProvider: authP,
team,
@@ -84,7 +89,7 @@ export default async function teamCreator({
});
}
const transaction = await sequelize.transaction();
const transaction = await Team.sequelize!.transaction();
let team;
try {

View File

@@ -9,18 +9,8 @@ import {
import { flushdb } from "@server/test/support";
import teamPermanentDeleter from "./teamPermanentDeleter";
jest.mock("aws-sdk", () => {
const mS3 = {
createPresignedPost: jest.fn(),
deleteObject: jest.fn().mockReturnThis(),
promise: jest.fn(),
};
return {
S3: jest.fn(() => mS3),
Endpoint: jest.fn(),
};
});
beforeEach(() => flushdb());
describe("teamPermanentDeleter", () => {
it("should destroy related data", async () => {
const team = await buildTeam({

View File

@@ -1,3 +1,4 @@
import { sequelize } from "@server/database/sequelize";
import Logger from "@server/logging/logger";
import {
ApiKey,
@@ -17,9 +18,7 @@ import {
SearchQuery,
Share,
} from "@server/models";
import { sequelize } from "../sequelize";
// @ts-expect-error ts-migrate(2749) FIXME: 'Team' refers to a value, but is being used as a t... Remove this comment to see the full error message
export default async function teamPermanentDeleter(team: Team) {
if (!team.deletedAt) {
throw new Error(
@@ -45,16 +44,14 @@ export default async function teamPermanentDeleter(team: Team) {
limit: 100,
offset: 0,
},
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'attachments' implicitly has an 'any' ty... Remove this comment to see the full error message
async (attachments, options) => {
Logger.info(
"commands",
`Deleting attachments ${options.offset} ${
options.offset + options.limit
(options.offset || 0) + (options?.limit || 0)
}`
);
await Promise.all(
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'attachment' implicitly has an 'any' typ... Remove this comment to see the full error message
attachments.map((attachment) =>
attachment.destroy({
// @ts-expect-error ts-migrate(7005) FIXME: Variable 'transaction' implicitly has an 'any' typ... Remove this comment to see the full error message
@@ -74,9 +71,7 @@ export default async function teamPermanentDeleter(team: Team) {
limit: 100,
offset: 0,
},
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'users' implicitly has an 'any' type.
async (users) => {
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'user' implicitly has an 'any' type.
const userIds = users.map((user) => user.id);
await UserAuthentication.destroy({
where: {

View File

@@ -3,12 +3,13 @@ import { flushdb } from "@server/test/support";
import userCreator from "./userCreator";
beforeEach(() => flushdb());
describe("userCreator", () => {
const ip = "127.0.0.1";
it("should update exising user and authentication", async () => {
const existing = await buildUser();
const authentications = await existing.getAuthentications();
const authentications = await existing.$get("authentications");
const existingAuth = authentications[0];
const newEmail = "test@example.com";
const newUsername = "tname";
@@ -37,7 +38,7 @@ describe("userCreator", () => {
it("should create user with deleted user matching providerId", async () => {
const existing = await buildUser();
const authentications = await existing.getAuthentications();
const authentications = await existing.$get("authentications");
const existingAuth = authentications[0];
const newEmail = "test@example.com";
await existing.destroy();
@@ -63,7 +64,7 @@ describe("userCreator", () => {
it("should handle duplicate providerId for different iDP", async () => {
const existing = await buildUser();
const authentications = await existing.getAuthentications();
const authentications = await existing.$get("authentications");
const existingAuth = authentications[0];
let error;
@@ -89,7 +90,7 @@ describe("userCreator", () => {
it("should create a new user", async () => {
const team = await buildTeam();
const authenticationProviders = await team.getAuthenticationProviders();
const authenticationProviders = await team.$get("authenticationProviders");
const authenticationProvider = authenticationProviders[0];
const result = await userCreator({
name: "Test Name",
@@ -119,7 +120,7 @@ describe("userCreator", () => {
const team = await buildTeam({
defaultUserRole: "viewer",
});
const authenticationProviders = await team.getAuthenticationProviders();
const authenticationProviders = await team.$get("authenticationProviders");
const authenticationProvider = authenticationProviders[0];
const result = await userCreator({
name: "Test Name",
@@ -143,7 +144,7 @@ describe("userCreator", () => {
const team = await buildTeam({
defaultUserRole: "viewer",
});
const authenticationProviders = await team.getAuthenticationProviders();
const authenticationProviders = await team.$get("authenticationProviders");
const authenticationProvider = authenticationProviders[0];
const result = await userCreator({
name: "Test Name",
@@ -187,11 +188,11 @@ describe("userCreator", () => {
const invite = await buildInvite({
teamId: team.id,
});
const authenticationProviders = await team.getAuthenticationProviders();
const authenticationProviders = await team.$get("authenticationProviders");
const authenticationProvider = authenticationProviders[0];
const result = await userCreator({
name: invite.name,
email: invite.email,
email: invite.email!,
teamId: invite.teamId,
ip,
authentication: {

View File

@@ -1,15 +1,29 @@
import { Op } from "sequelize";
import { Event, Team, User, UserAuthentication } from "@server/models";
import { sequelize } from "../sequelize";
type UserCreatorResult = {
// @ts-expect-error ts-migrate(2749) FIXME: 'User' refers to a value, but is being used as a t... Remove this comment to see the full error message
user: User;
isNewUser: boolean;
// @ts-expect-error ts-migrate(2749) FIXME: 'UserAuthentication' refers to a value, but is bei... Remove this comment to see the full error message
authentication: UserAuthentication;
};
type Props = {
name: string;
email: string;
username?: string;
isAdmin?: boolean;
avatarUrl?: string | null;
teamId: string;
ip: string;
authentication: {
authenticationProviderId: string;
providerId: string;
scopes: string[];
accessToken?: string;
refreshToken?: string;
};
};
export default async function userCreator({
name,
email,
@@ -19,22 +33,7 @@ export default async function userCreator({
teamId,
authentication,
ip,
}: {
name: string;
email: string;
username?: string;
isAdmin?: boolean;
avatarUrl?: string;
teamId: string;
ip: string;
authentication: {
authenticationProviderId: string;
providerId: string;
scopes: string[];
accessToken?: string;
refreshToken?: string;
};
}): Promise<UserCreatorResult> {
}: Props): Promise<UserCreatorResult> {
const { authenticationProviderId, providerId, ...rest } = authentication;
const auth = await UserAuthentication.findOne({
where: {
@@ -90,7 +89,7 @@ export default async function userCreator({
email,
teamId,
lastActiveAt: {
[Op.eq]: null,
[Op.is]: null,
},
},
include: [
@@ -105,7 +104,7 @@ export default async function userCreator({
// We have an existing invite for his user, so we need to update it with our
// new details and link up the authentication method
if (invite && !invite.authentications.length) {
const transaction = await sequelize.transaction();
const transaction = await User.sequelize!.transaction();
let auth;
try {
@@ -118,9 +117,13 @@ export default async function userCreator({
transaction,
}
);
auth = await invite.createAuthentication(authentication, {
transaction,
});
auth = await invite.$create<UserAuthentication>(
"authentication",
authentication,
{
transaction,
}
);
await transaction.commit();
} catch (err) {
await transaction.rollback();
@@ -135,13 +138,15 @@ export default async function userCreator({
}
// No auth, no user this is an entirely new sign in.
const transaction = await sequelize.transaction();
const transaction = await User.sequelize!.transaction();
try {
const { defaultUserRole } = await Team.findByPk(teamId, {
const team = await Team.findByPk(teamId, {
attributes: ["defaultUserRole"],
transaction,
});
const defaultUserRole = team?.defaultUserRole;
const user = await User.create(
{
name,

View File

@@ -3,6 +3,7 @@ import { flushdb } from "@server/test/support";
import userDestroyer from "./userDestroyer";
beforeEach(() => flushdb());
describe("userDestroyer", () => {
const ip = "127.0.0.1";

View File

@@ -1,15 +1,14 @@
import { Op } from "sequelize";
import { sequelize } from "@server/database/sequelize";
import { Event, User } from "@server/models";
import { ValidationError } from "../errors";
import { Op, sequelize } from "../sequelize";
export default async function userDestroyer({
user,
actor,
ip,
}: {
// @ts-expect-error ts-migrate(2749) FIXME: 'User' refers to a value, but is being used as a t... Remove this comment to see the full error message
user: User;
// @ts-expect-error ts-migrate(2749) FIXME: 'User' refers to a value, but is being used as a t... Remove this comment to see the full error message
actor: User;
ip: string;
}) {

View File

@@ -3,6 +3,7 @@ import { flushdb } from "@server/test/support";
import userInviter from "./userInviter";
beforeEach(() => flushdb());
describe("userInviter", () => {
const ip = "127.0.0.1";
@@ -10,8 +11,8 @@ describe("userInviter", () => {
const user = await buildUser();
const response = await userInviter({
invites: [
// @ts-expect-error ts-migrate(2741) FIXME: Property 'role' is missing in type '{ email: strin... Remove this comment to see the full error message
{
role: "member",
email: "test@example.com",
name: "Test",
},
@@ -26,8 +27,8 @@ describe("userInviter", () => {
const user = await buildUser();
const response = await userInviter({
invites: [
// @ts-expect-error ts-migrate(2741) FIXME: Property 'role' is missing in type '{ email: strin... Remove this comment to see the full error message
{
role: "member",
email: " ",
name: "Test",
},
@@ -42,8 +43,8 @@ describe("userInviter", () => {
const user = await buildUser();
const response = await userInviter({
invites: [
// @ts-expect-error ts-migrate(2741) FIXME: Property 'role' is missing in type '{ email: strin... Remove this comment to see the full error message
{
role: "member",
email: "notanemail",
name: "Test",
},
@@ -58,13 +59,13 @@ describe("userInviter", () => {
const user = await buildUser();
const response = await userInviter({
invites: [
// @ts-expect-error ts-migrate(2741) FIXME: Property 'role' is missing in type '{ email: strin... Remove this comment to see the full error message
{
role: "member",
email: "the@same.com",
name: "Test",
},
// @ts-expect-error ts-migrate(2741) FIXME: Property 'role' is missing in type '{ email: strin... Remove this comment to see the full error message
{
role: "member",
email: "the@SAME.COM",
name: "Test",
},
@@ -79,9 +80,9 @@ describe("userInviter", () => {
const user = await buildUser();
const response = await userInviter({
invites: [
// @ts-expect-error ts-migrate(2741) FIXME: Property 'role' is missing in type '{ email: any; ... Remove this comment to see the full error message
{
email: user.email,
role: "member",
email: user.email!,
name: user.name,
},
],

View File

@@ -1,7 +1,8 @@
import invariant from "invariant";
import { uniqBy } from "lodash";
import { Role } from "@shared/types";
import mailer from "@server/mailer";
import { User, Event, Team } from "@server/models";
import mailer from "../mailer";
type Invite = {
name: string;
@@ -14,16 +15,16 @@ export default async function userInviter({
invites,
ip,
}: {
// @ts-expect-error ts-migrate(2749) FIXME: 'User' refers to a value, but is being used as a t... Remove this comment to see the full error message
user: User;
invites: Invite[];
ip: string;
}): Promise<{
sent: Invite[];
// @ts-expect-error ts-migrate(2749) FIXME: 'User' refers to a value, but is being used as a t... Remove this comment to see the full error message
users: User[];
}> {
const team = await Team.findByPk(user.teamId);
invariant(team, "team not found");
// filter out empties and obvious non-emails
const compactedInvites = invites.filter(
(invite) => !!invite.email.trim() && invite.email.match("@")
@@ -44,7 +45,6 @@ export default async function userInviter({
email: emails,
},
});
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'user' implicitly has an 'any' type.
const existingEmails = existingUsers.map((user) => user.email);
const filteredInvites = normalizedInvites.filter(
(invite) => !existingEmails.includes(invite.email)

View File

@@ -1,9 +1,10 @@
import { GroupUser } from "@server/models";
import GroupUser from "@server/models/GroupUser";
import { buildGroup, buildAdmin, buildUser } from "@server/test/factories";
import { flushdb } from "@server/test/support";
import userSuspender from "./userSuspender";
beforeEach(() => flushdb());
describe("userSuspender", () => {
const ip = "127.0.0.1";
@@ -46,7 +47,7 @@ describe("userSuspender", () => {
const group = await buildGroup({
teamId: user.teamId,
});
await group.addUser(user, {
await group.$add("user", user, {
through: {
createdById: user.id,
},

View File

@@ -1,18 +1,19 @@
import { Transaction } from "sequelize";
import { sequelize } from "@server/database/sequelize";
import { User, Event, GroupUser } from "@server/models";
import { ValidationError } from "../errors";
import { sequelize } from "../sequelize";
type Props = {
user: User;
actorId: string;
ip: string;
};
export default async function userSuspender({
user,
actorId,
ip,
}: {
// @ts-expect-error ts-migrate(2749) FIXME: 'User' refers to a value, but is being used as a t... Remove this comment to see the full error message
user: User;
actorId: string;
ip: string;
}): Promise<void> {
}: Props): Promise<void> {
if (user.id === actorId) {
throw ValidationError("Unable to suspend the current user");
}