mirror of
https://github.com/outline/outline.git
synced 2025-12-21 10:39:41 -06:00
perf: Remove documentStructure from default query select (#9141)
* perf: Remove documentStructure from default query select * test
This commit is contained in:
@@ -228,6 +228,7 @@
|
||||
"sequelize": "^6.37.3",
|
||||
"sequelize-cli": "^6.6.2",
|
||||
"sequelize-encrypted": "^1.0.0",
|
||||
"sequelize-strict-attributes": "^1.0.2",
|
||||
"sequelize-typescript": "^2.1.6",
|
||||
"slug": "^5.3.0",
|
||||
"slugify": "^1.6.6",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import invariant from "invariant";
|
||||
import { Op, WhereOptions } from "sequelize";
|
||||
import isUUID from "validator/lib/isUUID";
|
||||
import { UrlHelper } from "@shared/utils/UrlHelper";
|
||||
@@ -22,8 +21,8 @@ type Props = {
|
||||
|
||||
type Result = {
|
||||
document: Document;
|
||||
share?: Share;
|
||||
collection?: Collection | null;
|
||||
share: Share | null;
|
||||
collection: Collection | null;
|
||||
};
|
||||
|
||||
export default async function loadDocument({
|
||||
@@ -33,9 +32,9 @@ export default async function loadDocument({
|
||||
user,
|
||||
includeState,
|
||||
}: Props): Promise<Result> {
|
||||
let document;
|
||||
let collection;
|
||||
let share;
|
||||
let document: Document | null = null;
|
||||
let collection: Collection | null = null;
|
||||
let share: Share | null = null;
|
||||
|
||||
if (!shareId && !(id && user)) {
|
||||
throw AuthenticationError(`Authentication or shareId required`);
|
||||
@@ -72,20 +71,7 @@ export default async function loadDocument({
|
||||
where: whereClause,
|
||||
include: [
|
||||
{
|
||||
// unscoping here allows us to return unpublished documents
|
||||
model: Document.unscoped(),
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: "createdBy",
|
||||
paranoid: false,
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: "updatedBy",
|
||||
paranoid: false,
|
||||
},
|
||||
],
|
||||
model: Document.scope("withDrafts"),
|
||||
required: true,
|
||||
as: "document",
|
||||
},
|
||||
@@ -129,14 +115,13 @@ export default async function loadDocument({
|
||||
const canReadDocument = user && can(user, "read", document);
|
||||
|
||||
if (canReadDocument) {
|
||||
// Cannot use document.collection here as it does not include the
|
||||
// documentStructure by default through the relationship.
|
||||
if (document.collectionId) {
|
||||
collection = await Collection.findByPk(document.collectionId);
|
||||
|
||||
if (!collection) {
|
||||
throw NotFoundError("Collection could not be found for document");
|
||||
}
|
||||
collection = await Collection.scope("withDocumentStructure").findByPk(
|
||||
document.collectionId,
|
||||
{
|
||||
rejectOnEmpty: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -155,11 +140,15 @@ export default async function loadDocument({
|
||||
|
||||
// It is possible to disable sharing at the collection so we must check
|
||||
if (document.collectionId) {
|
||||
collection = await Collection.findByPk(document.collectionId);
|
||||
collection = await Collection.scope("withDocumentStructure").findByPk(
|
||||
document.collectionId,
|
||||
{
|
||||
rejectOnEmpty: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
invariant(collection, "collection not found");
|
||||
|
||||
if (!collection.sharing) {
|
||||
if (!collection?.sharing) {
|
||||
throw AuthorizationError();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import invariant from "invariant";
|
||||
import { Transaction } from "sequelize";
|
||||
import { createContext } from "@server/context";
|
||||
import { traceFunction } from "@server/logging/tracing";
|
||||
@@ -66,16 +65,21 @@ async function documentMover({
|
||||
result.documents.push(document);
|
||||
} else {
|
||||
// Load the current and the next collection upfront and lock them
|
||||
const collection = await Collection.findByPk(document.collectionId!, {
|
||||
transaction,
|
||||
lock: Transaction.LOCK.UPDATE,
|
||||
paranoid: false,
|
||||
});
|
||||
const collection = await Collection.scope("withDocumentStructure").findByPk(
|
||||
document.collectionId!,
|
||||
{
|
||||
transaction,
|
||||
lock: Transaction.LOCK.UPDATE,
|
||||
paranoid: false,
|
||||
}
|
||||
);
|
||||
|
||||
let newCollection = collection;
|
||||
if (collectionChanged) {
|
||||
if (collectionId) {
|
||||
newCollection = await Collection.findByPk(collectionId, {
|
||||
newCollection = await Collection.scope(
|
||||
"withDocumentStructure"
|
||||
).findByPk(collectionId, {
|
||||
transaction,
|
||||
lock: Transaction.LOCK.UPDATE,
|
||||
});
|
||||
@@ -144,12 +148,14 @@ async function documentMover({
|
||||
|
||||
if (collectionId) {
|
||||
// Reload the collection to get relationship data
|
||||
newCollection = await Collection.scope({
|
||||
method: ["withMembership", user.id],
|
||||
}).findByPk(collectionId, {
|
||||
newCollection = await Collection.scope([
|
||||
{
|
||||
method: ["withMembership", user.id],
|
||||
},
|
||||
]).findByPk(collectionId, {
|
||||
transaction,
|
||||
rejectOnEmpty: true,
|
||||
});
|
||||
invariant(newCollection, "Collection not found");
|
||||
|
||||
result.collections.push(newCollection);
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
describe("#url", () => {
|
||||
test("should return correct url for the collection", () => {
|
||||
it("should return correct url for the collection", () => {
|
||||
const collection = new Collection({
|
||||
id: "1234",
|
||||
});
|
||||
@@ -25,7 +25,7 @@ describe("#url", () => {
|
||||
});
|
||||
|
||||
describe("getDocumentParents", () => {
|
||||
test("should return array of parent document ids", async () => {
|
||||
it("should return array of parent document ids", async () => {
|
||||
const parent = await buildDocument();
|
||||
const document = await buildDocument();
|
||||
const collection = await buildCollection({
|
||||
@@ -41,7 +41,7 @@ describe("getDocumentParents", () => {
|
||||
expect(result ? result[0] : undefined).toBe(parent.id);
|
||||
});
|
||||
|
||||
test("should return array of parent document ids", async () => {
|
||||
it("should return array of parent document ids", async () => {
|
||||
const parent = await buildDocument();
|
||||
const document = await buildDocument();
|
||||
const collection = await buildCollection({
|
||||
@@ -56,7 +56,7 @@ describe("getDocumentParents", () => {
|
||||
expect(result?.length).toBe(0);
|
||||
});
|
||||
|
||||
test("should not error if documentStructure is empty", async () => {
|
||||
it("should not error if documentStructure is empty", async () => {
|
||||
const parent = await buildDocument();
|
||||
await buildDocument();
|
||||
const collection = await buildCollection();
|
||||
@@ -66,7 +66,7 @@ describe("getDocumentParents", () => {
|
||||
});
|
||||
|
||||
describe("getDocumentTree", () => {
|
||||
test("should return document tree", async () => {
|
||||
it("should return document tree", async () => {
|
||||
const document = await buildDocument();
|
||||
const collection = await buildCollection({
|
||||
documentStructure: [await document.toNavigationNode()],
|
||||
@@ -76,7 +76,7 @@ describe("getDocumentTree", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("should return nested documents in tree", async () => {
|
||||
it("should return nested documents in tree", async () => {
|
||||
const parent = await buildDocument();
|
||||
const document = await buildDocument();
|
||||
const collection = await buildCollection({
|
||||
@@ -99,7 +99,7 @@ describe("getDocumentTree", () => {
|
||||
});
|
||||
|
||||
describe("#addDocumentToStructure", () => {
|
||||
test("should add as last element without index", async () => {
|
||||
it("should add as last element without index", async () => {
|
||||
const collection = await buildCollection();
|
||||
const id = uuidv4();
|
||||
const newDocument = await buildDocument({
|
||||
@@ -117,7 +117,7 @@ describe("#addDocumentToStructure", () => {
|
||||
expect(collection.documentStructure!.length).toBe(1);
|
||||
});
|
||||
|
||||
test("should add with an index", async () => {
|
||||
it("should add with an index", async () => {
|
||||
const collection = await buildCollection();
|
||||
const id = uuidv4();
|
||||
const newDocument = await buildDocument({
|
||||
@@ -131,7 +131,7 @@ describe("#addDocumentToStructure", () => {
|
||||
expect(collection.documentStructure![0].id).toBe(id);
|
||||
});
|
||||
|
||||
test("should add as a child if with parent", async () => {
|
||||
it("should add as a child if with parent", async () => {
|
||||
const collection = await buildCollection();
|
||||
const document = await buildDocument({ collectionId: collection.id });
|
||||
await collection.reload();
|
||||
@@ -150,7 +150,7 @@ describe("#addDocumentToStructure", () => {
|
||||
expect(collection.documentStructure![0].children[0].id).toBe(id);
|
||||
});
|
||||
|
||||
test("should add as a child if with parent with index", async () => {
|
||||
it("should add as a child if with parent with index", async () => {
|
||||
const collection = await buildCollection();
|
||||
const document = await buildDocument({ collectionId: collection.id });
|
||||
await collection.reload();
|
||||
@@ -176,7 +176,7 @@ describe("#addDocumentToStructure", () => {
|
||||
expect(collection.documentStructure![0].children[0].id).toBe(id);
|
||||
});
|
||||
|
||||
test("should add the document along with its nested document(s)", async () => {
|
||||
it("should add the document along with its nested document(s)", async () => {
|
||||
const collection = await buildCollection();
|
||||
|
||||
const document = await buildDocument({
|
||||
@@ -204,7 +204,7 @@ describe("#addDocumentToStructure", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("should add the document along with its archived nested document(s)", async () => {
|
||||
it("should add the document along with its archived nested document(s)", async () => {
|
||||
const collection = await buildCollection();
|
||||
|
||||
const document = await buildDocument({
|
||||
@@ -237,7 +237,7 @@ describe("#addDocumentToStructure", () => {
|
||||
);
|
||||
});
|
||||
describe("options: documentJson", () => {
|
||||
test("should append supplied json over document's own", async () => {
|
||||
it("should append supplied json over document's own", async () => {
|
||||
const collection = await buildCollection();
|
||||
const id = uuidv4();
|
||||
const newDocument = await buildDocument({
|
||||
@@ -268,7 +268,7 @@ describe("#addDocumentToStructure", () => {
|
||||
});
|
||||
|
||||
describe("#updateDocument", () => {
|
||||
test("should update root document's data", async () => {
|
||||
it("should update root document's data", async () => {
|
||||
const collection = await buildCollection();
|
||||
const document = await buildDocument({ collectionId: collection.id });
|
||||
await collection.reload();
|
||||
@@ -279,7 +279,7 @@ describe("#updateDocument", () => {
|
||||
expect(collection.documentStructure![0].title).toBe("Updated title");
|
||||
});
|
||||
|
||||
test("should update child document's data", async () => {
|
||||
it("should update child document's data", async () => {
|
||||
const collection = await buildCollection();
|
||||
const document = await buildDocument({ collectionId: collection.id });
|
||||
await collection.reload();
|
||||
@@ -297,7 +297,7 @@ describe("#updateDocument", () => {
|
||||
newDocument.title = "Updated title";
|
||||
await newDocument.save();
|
||||
await collection.updateDocument(newDocument);
|
||||
const reloaded = await Collection.findByPk(collection.id);
|
||||
const reloaded = await collection.reload();
|
||||
expect(reloaded!.documentStructure![0].children[0].title).toBe(
|
||||
"Updated title"
|
||||
);
|
||||
@@ -305,7 +305,7 @@ describe("#updateDocument", () => {
|
||||
});
|
||||
|
||||
describe("#removeDocument", () => {
|
||||
test("should save if removing", async () => {
|
||||
it("should save if removing", async () => {
|
||||
const collection = await buildCollection();
|
||||
const document = await buildDocument({ collectionId: collection.id });
|
||||
await collection.reload();
|
||||
@@ -315,7 +315,7 @@ describe("#removeDocument", () => {
|
||||
expect(collection.save).toBeCalled();
|
||||
});
|
||||
|
||||
test("should remove documents from root", async () => {
|
||||
it("should remove documents from root", async () => {
|
||||
const collection = await buildCollection();
|
||||
const document = await buildDocument({ collectionId: collection.id });
|
||||
await collection.reload();
|
||||
@@ -331,7 +331,7 @@ describe("#removeDocument", () => {
|
||||
expect(collectionDocuments.count).toBe(0);
|
||||
});
|
||||
|
||||
test("should remove a document with child documents", async () => {
|
||||
it("should remove a document with child documents", async () => {
|
||||
const collection = await buildCollection();
|
||||
const document = await buildDocument({ collectionId: collection.id });
|
||||
await collection.reload();
|
||||
@@ -359,7 +359,7 @@ describe("#removeDocument", () => {
|
||||
expect(collectionDocuments.count).toBe(0);
|
||||
});
|
||||
|
||||
test("should remove a child document", async () => {
|
||||
it("should remove a child document", async () => {
|
||||
const collection = await buildCollection();
|
||||
const document = await buildDocument({ collectionId: collection.id });
|
||||
await collection.reload();
|
||||
@@ -380,7 +380,7 @@ describe("#removeDocument", () => {
|
||||
expect(collection.documentStructure![0].children.length).toBe(1);
|
||||
// Remove the document
|
||||
await collection.deleteDocument(newDocument);
|
||||
const reloaded = await Collection.findByPk(collection.id);
|
||||
const reloaded = await collection.reload();
|
||||
expect(reloaded!.documentStructure!.length).toBe(1);
|
||||
expect(reloaded!.documentStructure![0].children.length).toBe(0);
|
||||
const collectionDocuments = await Document.findAndCountAll({
|
||||
@@ -393,7 +393,7 @@ describe("#removeDocument", () => {
|
||||
});
|
||||
|
||||
describe("#membershipUserIds", () => {
|
||||
test("should return collection and group memberships", async () => {
|
||||
it("should return collection and group memberships", async () => {
|
||||
const team = await buildTeam();
|
||||
const teamId = team.id;
|
||||
// Make 6 users
|
||||
@@ -464,47 +464,53 @@ describe("#membershipUserIds", () => {
|
||||
});
|
||||
|
||||
describe("#findByPk", () => {
|
||||
test("should return collection with collection Id", async () => {
|
||||
it("should return collection with collection Id", async () => {
|
||||
const collection = await buildCollection();
|
||||
const response = await Collection.findByPk(collection.id);
|
||||
expect(response!.id).toBe(collection.id);
|
||||
});
|
||||
|
||||
test("should return collection when urlId is present", async () => {
|
||||
it("should not return documentStructure by default", async () => {
|
||||
const collection = await buildCollection();
|
||||
const response = await Collection.findByPk(collection.id);
|
||||
expect(() => response!.documentStructure).toThrow();
|
||||
});
|
||||
|
||||
it("should return collection when urlId is present", async () => {
|
||||
const collection = await buildCollection();
|
||||
const id = `${slugify(collection.name)}-${collection.urlId}`;
|
||||
const response = await Collection.findByPk(id);
|
||||
expect(response!.id).toBe(collection.id);
|
||||
});
|
||||
|
||||
test("should return collection when urlId is present, but missing slug", async () => {
|
||||
it("should return collection when urlId is present, but missing slug", async () => {
|
||||
const collection = await buildCollection();
|
||||
const id = collection.urlId;
|
||||
const response = await Collection.findByPk(id);
|
||||
expect(response!.id).toBe(collection.id);
|
||||
});
|
||||
|
||||
test("should return null when incorrect uuid type", async () => {
|
||||
it("should return null when incorrect uuid type", async () => {
|
||||
const collection = await buildCollection();
|
||||
const response = await Collection.findByPk(collection.id + "-incorrect");
|
||||
expect(response).toBe(null);
|
||||
});
|
||||
|
||||
test("should return null when incorrect urlId length", async () => {
|
||||
it("should return null when incorrect urlId length", async () => {
|
||||
const collection = await buildCollection();
|
||||
const id = `${slugify(collection.name)}-${collection.urlId}incorrect`;
|
||||
const response = await Collection.findByPk(id);
|
||||
expect(response).toBe(null);
|
||||
});
|
||||
|
||||
test("should return null when no collection is found with uuid", async () => {
|
||||
it("should return null when no collection is found with uuid", async () => {
|
||||
const response = await Collection.findByPk(
|
||||
"a9e71a81-7342-4ea3-9889-9b9cc8f667da"
|
||||
);
|
||||
expect(response).toBe(null);
|
||||
});
|
||||
|
||||
test("should return null when no collection is found with urlId", async () => {
|
||||
it("should return null when no collection is found with urlId", async () => {
|
||||
const id = `${slugify("test collection")}-${randomstring.generate(15)}`;
|
||||
const response = await Collection.findByPk(id);
|
||||
expect(response).toBe(null);
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
AllowNull,
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
DefaultScope,
|
||||
} from "sequelize-typescript";
|
||||
import isUUID from "validator/lib/isUUID";
|
||||
import type { CollectionSort, ProsemirrorData } from "@shared/types";
|
||||
@@ -69,6 +70,11 @@ type AdditionalFindOptions = {
|
||||
rejectOnEmpty?: boolean | Error;
|
||||
};
|
||||
|
||||
@DefaultScope(() => ({
|
||||
attributes: {
|
||||
exclude: ["documentStructure"],
|
||||
},
|
||||
}))
|
||||
@Scopes(() => ({
|
||||
withAllMemberships: {
|
||||
include: [
|
||||
@@ -121,6 +127,12 @@ type AdditionalFindOptions = {
|
||||
},
|
||||
],
|
||||
}),
|
||||
withDocumentStructure: () => ({
|
||||
attributes: {
|
||||
// resets to include the documentStructure column
|
||||
exclude: [],
|
||||
},
|
||||
}),
|
||||
withMembership: (userId: string) => {
|
||||
if (!userId) {
|
||||
return {};
|
||||
@@ -238,6 +250,7 @@ class Collection extends ParanoidModel<
|
||||
@Column
|
||||
maintainerApprovalRequired: boolean;
|
||||
|
||||
@Default(null)
|
||||
@Column(DataType.JSONB)
|
||||
documentStructure: NavigationNode[] | null;
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
buildUser,
|
||||
buildGuestUser,
|
||||
} from "@server/test/factories";
|
||||
import Collection from "./Collection";
|
||||
import UserMembership from "./UserMembership";
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -96,10 +95,8 @@ describe("#delete", () => {
|
||||
|
||||
await document.delete(user);
|
||||
const [newDocument, newCollection] = await Promise.all([
|
||||
Document.findByPk(document.id, {
|
||||
paranoid: false,
|
||||
}),
|
||||
Collection.findByPk(collection.id),
|
||||
document.reload({ paranoid: false }),
|
||||
collection.reload(),
|
||||
]);
|
||||
|
||||
expect(newDocument?.lastModifiedById).toEqual(user.id);
|
||||
|
||||
@@ -162,11 +162,13 @@ type AdditionalFindOptions = {
|
||||
return {
|
||||
include: [
|
||||
{
|
||||
attributes: ["id", "permission", "sharing", "teamId", "deletedAt"],
|
||||
model: userId
|
||||
? Collection.scope({
|
||||
method: ["withMembership", userId],
|
||||
})
|
||||
? Collection.scope([
|
||||
"defaultScope",
|
||||
{
|
||||
method: ["withMembership", userId],
|
||||
},
|
||||
])
|
||||
: Collection,
|
||||
as: "collection",
|
||||
paranoid,
|
||||
@@ -414,10 +416,13 @@ class Document extends ArchivableModel<
|
||||
return;
|
||||
}
|
||||
|
||||
const collection = await Collection.findByPk(model.collectionId, {
|
||||
transaction,
|
||||
lock: Transaction.LOCK.UPDATE,
|
||||
});
|
||||
const collection = await Collection.scope("withDocumentStructure").findByPk(
|
||||
model.collectionId,
|
||||
{
|
||||
transaction,
|
||||
lock: Transaction.LOCK.UPDATE,
|
||||
}
|
||||
);
|
||||
if (!collection) {
|
||||
return;
|
||||
}
|
||||
@@ -438,7 +443,9 @@ class Document extends ArchivableModel<
|
||||
}
|
||||
|
||||
return this.sequelize!.transaction(async (transaction: Transaction) => {
|
||||
const collection = await Collection.findByPk(model.collectionId!, {
|
||||
const collection = await Collection.scope(
|
||||
"withDocumentStructure"
|
||||
).findByPk(model.collectionId!, {
|
||||
transaction,
|
||||
lock: transaction.LOCK.UPDATE,
|
||||
});
|
||||
@@ -926,7 +933,9 @@ class Document extends ArchivableModel<
|
||||
}
|
||||
|
||||
if (!this.template && this.collectionId) {
|
||||
const collection = await Collection.findByPk(this.collectionId, {
|
||||
const collection = await Collection.scope(
|
||||
"withDocumentStructure"
|
||||
).findByPk(this.collectionId, {
|
||||
transaction,
|
||||
lock: Transaction.LOCK.UPDATE,
|
||||
});
|
||||
@@ -993,10 +1002,13 @@ class Document extends ArchivableModel<
|
||||
|
||||
await this.sequelize.transaction(async (transaction: Transaction) => {
|
||||
const collection = this.collectionId
|
||||
? await Collection.findByPk(this.collectionId, {
|
||||
transaction,
|
||||
lock: transaction.LOCK.UPDATE,
|
||||
})
|
||||
? await Collection.scope("withDocumentStructure").findByPk(
|
||||
this.collectionId,
|
||||
{
|
||||
transaction,
|
||||
lock: transaction.LOCK.UPDATE,
|
||||
}
|
||||
)
|
||||
: undefined;
|
||||
|
||||
if (collection) {
|
||||
@@ -1027,10 +1039,13 @@ class Document extends ArchivableModel<
|
||||
archive = async (user: User, options?: FindOptions) => {
|
||||
const { transaction } = { ...options };
|
||||
const collection = this.collectionId
|
||||
? await Collection.findByPk(this.collectionId, {
|
||||
transaction,
|
||||
lock: transaction?.LOCK.UPDATE,
|
||||
})
|
||||
? await Collection.scope("withDocumentStructure").findByPk(
|
||||
this.collectionId,
|
||||
{
|
||||
transaction,
|
||||
lock: transaction?.LOCK.UPDATE,
|
||||
}
|
||||
)
|
||||
: undefined;
|
||||
|
||||
if (collection) {
|
||||
@@ -1051,7 +1066,7 @@ class Document extends ArchivableModel<
|
||||
) => {
|
||||
const { transaction } = { ...options };
|
||||
const collection = collectionId
|
||||
? await Collection.findByPk(collectionId, {
|
||||
? await Collection.scope("withDocumentStructure").findByPk(collectionId, {
|
||||
transaction,
|
||||
lock: transaction?.LOCK.UPDATE,
|
||||
})
|
||||
@@ -1103,7 +1118,9 @@ class Document extends ArchivableModel<
|
||||
let deleted = false;
|
||||
|
||||
if (!this.template && this.collectionId) {
|
||||
const collection = await Collection.findByPk(this.collectionId!, {
|
||||
const collection = await Collection.scope(
|
||||
"withDocumentStructure"
|
||||
).findByPk(this.collectionId!, {
|
||||
transaction,
|
||||
lock: transaction.LOCK.UPDATE,
|
||||
paranoid: false,
|
||||
|
||||
@@ -140,9 +140,11 @@ router.post(
|
||||
async (ctx: APIContext<T.CollectionsDocumentsReq>) => {
|
||||
const { id } = ctx.input.body;
|
||||
const { user } = ctx.state.auth;
|
||||
const collection = await Collection.scope({
|
||||
method: ["withMembership", user.id],
|
||||
}).findByPk(id);
|
||||
const collection = await Collection.scope([
|
||||
{
|
||||
method: ["withMembership", user.id],
|
||||
},
|
||||
]).findByPk(id);
|
||||
|
||||
authorize(user, "readDocument", collection);
|
||||
|
||||
|
||||
@@ -977,7 +977,7 @@ describe("#documents.list", () => {
|
||||
const res = await server.post("/api/documents.list", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
collection: document.collectionId,
|
||||
collectionId: document.collectionId,
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
@@ -1013,7 +1013,7 @@ describe("#documents.list", () => {
|
||||
const res = await server.post("/api/documents.list", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
collection: collection.id,
|
||||
collectionId: collection.id,
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
|
||||
@@ -133,15 +133,19 @@ router.post(
|
||||
// if a specific collection is passed then we need to check auth to view it
|
||||
if (collectionId) {
|
||||
where[Op.and].push({ collectionId: [collectionId] });
|
||||
const collection = await Collection.scope({
|
||||
method: ["withMembership", user.id],
|
||||
}).findByPk(collectionId);
|
||||
const collection = await Collection.scope([
|
||||
sort === "index" ? "withDocumentStructure" : "defaultScope",
|
||||
{
|
||||
method: ["withMembership", user.id],
|
||||
},
|
||||
]).findByPk(collectionId);
|
||||
|
||||
authorize(user, "readDocument", collection);
|
||||
|
||||
// index sort is special because it uses the order of the documents in the
|
||||
// collection.documentStructure rather than a database column
|
||||
if (sort === "index") {
|
||||
documentIds = (collection?.documentStructure || [])
|
||||
documentIds = (collection.documentStructure || [])
|
||||
.map((node) => node.id)
|
||||
.slice(ctx.state.pagination.offset, ctx.state.pagination.limit);
|
||||
where[Op.and].push({ id: documentIds });
|
||||
|
||||
@@ -54,7 +54,11 @@ router.post(
|
||||
});
|
||||
authorize(user, "read", document);
|
||||
|
||||
const collection = await document.$get("collection");
|
||||
const collection = document.collectionId
|
||||
? await Collection.scope("withDocumentStructure").findByPk(
|
||||
document.collectionId
|
||||
)
|
||||
: undefined;
|
||||
const parentIds = collection?.getDocumentParents(documentId);
|
||||
const parentShare = parentIds
|
||||
? await Share.scope({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import path from "path";
|
||||
import { InferAttributes, InferCreationAttributes } from "sequelize";
|
||||
import sequelizeStrictAttributes from "sequelize-strict-attributes";
|
||||
import { Sequelize } from "sequelize-typescript";
|
||||
import { Umzug, SequelizeStorage, MigrationError } from "umzug";
|
||||
import env from "@server/env";
|
||||
@@ -23,7 +24,7 @@ export function createDatabaseInstance(
|
||||
}
|
||||
): Sequelize {
|
||||
try {
|
||||
return new Sequelize(databaseUrl, {
|
||||
const instance = new Sequelize(databaseUrl, {
|
||||
logging: (msg) =>
|
||||
process.env.DEBUG?.includes("database") &&
|
||||
Logger.debug("database", msg),
|
||||
@@ -47,6 +48,8 @@ export function createDatabaseInstance(
|
||||
},
|
||||
schema,
|
||||
});
|
||||
sequelizeStrictAttributes(instance);
|
||||
return instance;
|
||||
} catch (error) {
|
||||
Logger.fatal(
|
||||
"Could not connect to database",
|
||||
|
||||
@@ -310,7 +310,7 @@ export async function buildCollection(
|
||||
overrides.permission = CollectionPermission.ReadWrite;
|
||||
}
|
||||
|
||||
return Collection.create({
|
||||
return Collection.scope("withDocumentStructure").create({
|
||||
name: faker.lorem.words(2),
|
||||
description: faker.lorem.words(4),
|
||||
createdById: overrides.userId,
|
||||
@@ -416,7 +416,9 @@ export async function buildDocument(
|
||||
|
||||
if (overrides.collectionId && overrides.publishedAt !== null) {
|
||||
collection = collection
|
||||
? await Collection.findByPk(overrides.collectionId)
|
||||
? await Collection.scope("withDocumentStructure").findByPk(
|
||||
overrides.collectionId
|
||||
)
|
||||
: undefined;
|
||||
|
||||
await collection?.addDocumentToStructure(document, 0);
|
||||
|
||||
@@ -11,7 +11,6 @@ export async function collectionIndexing(
|
||||
where: {
|
||||
teamId,
|
||||
},
|
||||
attributes: ["id", "index", "name", "teamId"],
|
||||
transaction,
|
||||
});
|
||||
|
||||
|
||||
44
yarn.lock
44
yarn.lock
@@ -737,14 +737,7 @@
|
||||
"@jridgewell/trace-mapping" "^0.3.25"
|
||||
jsesc "^3.0.2"
|
||||
|
||||
"@babel/helper-annotate-as-pure@^7.22.5", "@babel/helper-annotate-as-pure@^7.25.9":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4"
|
||||
integrity sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==
|
||||
dependencies:
|
||||
"@babel/types" "^7.25.9"
|
||||
|
||||
"@babel/helper-annotate-as-pure@^7.27.1":
|
||||
"@babel/helper-annotate-as-pure@^7.22.5", "@babel/helper-annotate-as-pure@^7.25.9", "@babel/helper-annotate-as-pure@^7.27.1":
|
||||
version "7.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.1.tgz#4345d81a9a46a6486e24d069469f13e60445c05d"
|
||||
integrity sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==
|
||||
@@ -775,16 +768,7 @@
|
||||
"@babel/traverse" "^7.27.1"
|
||||
semver "^6.3.1"
|
||||
|
||||
"@babel/helper-create-regexp-features-plugin@^7.18.6":
|
||||
version "7.26.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz#5169756ecbe1d95f7866b90bb555b022595302a0"
|
||||
integrity sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==
|
||||
dependencies:
|
||||
"@babel/helper-annotate-as-pure" "^7.25.9"
|
||||
regexpu-core "^6.2.0"
|
||||
semver "^6.3.1"
|
||||
|
||||
"@babel/helper-create-regexp-features-plugin@^7.27.1":
|
||||
"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.27.1":
|
||||
version "7.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz#05b0882d97ba1d4d03519e4bce615d70afa18c53"
|
||||
integrity sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==
|
||||
@@ -826,15 +810,7 @@
|
||||
"@babel/traverse" "^7.27.1"
|
||||
"@babel/types" "^7.27.1"
|
||||
|
||||
"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.14.5", "@babel/helper-module-imports@^7.22.5":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715"
|
||||
integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==
|
||||
dependencies:
|
||||
"@babel/traverse" "^7.25.9"
|
||||
"@babel/types" "^7.25.9"
|
||||
|
||||
"@babel/helper-module-imports@^7.27.1":
|
||||
"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.14.5", "@babel/helper-module-imports@^7.22.5", "@babel/helper-module-imports@^7.27.1":
|
||||
version "7.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204"
|
||||
integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==
|
||||
@@ -1093,20 +1069,13 @@
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.14.5"
|
||||
|
||||
"@babel/plugin-syntax-typescript@^7.27.1":
|
||||
"@babel/plugin-syntax-typescript@^7.27.1", "@babel/plugin-syntax-typescript@^7.7.2":
|
||||
version "7.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz#5147d29066a793450f220c63fa3a9431b7e6dd18"
|
||||
integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.27.1"
|
||||
|
||||
"@babel/plugin-syntax-typescript@^7.7.2":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz#67dda2b74da43727cf21d46cf9afef23f4365399"
|
||||
integrity sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.25.9"
|
||||
|
||||
"@babel/plugin-syntax-unicode-sets-regex@^7.18.6":
|
||||
version "7.18.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357"
|
||||
@@ -14294,6 +14263,11 @@ sequelize-pool@^7.1.0:
|
||||
resolved "https://registry.yarnpkg.com/sequelize-pool/-/sequelize-pool-7.1.0.tgz#210b391af4002762f823188fd6ecfc7413020768"
|
||||
integrity "sha1-IQs5GvQAJ2L4IxiP1uz8dBMCB2g= sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg=="
|
||||
|
||||
sequelize-strict-attributes@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/sequelize-strict-attributes/-/sequelize-strict-attributes-1.0.2.tgz#d51c5322551abe59e95249011fdb335c513ade53"
|
||||
integrity sha512-FAzGrB4BAQzLD0xkGvAhWQf0Y8IRSn8ydaG6j8D8eMxT2z8iKIMzDdxtm9YFhHrTkLd+ILRktQLhEmGm921QvA==
|
||||
|
||||
sequelize-typescript@^2.1.6:
|
||||
version "2.1.6"
|
||||
resolved "https://registry.yarnpkg.com/sequelize-typescript/-/sequelize-typescript-2.1.6.tgz#9476c8a2510114ed1c3a26b424c47e05c2e6284e"
|
||||
|
||||
Reference in New Issue
Block a user