fix: Index calculation does not take into account user scope (#7294)

* fix: Index calculation does not take into account user scope

* Standardize sort index maxlength to 256
This commit is contained in:
Tom Moor
2024-07-23 07:20:30 -04:00
committed by GitHub
parent 10b33ff91f
commit 1426c4e6ab
6 changed files with 38 additions and 28 deletions
+10 -4
View File
@@ -197,8 +197,8 @@ class Collection extends ParanoidModel<
color: string | null;
@Length({
max: 100,
msg: `index must be 100 characters or less`,
max: 256,
msg: `index must be 256 characters or less`,
})
@Column
index: string | null;
@@ -425,13 +425,18 @@ class Collection extends ParanoidModel<
/**
* Find the first collection that the specified user has access to.
*
* @param user User object
* @param user User to find the collection for
* @param options Additional options for the query
* @returns collection First collection in the sidebar order
*/
static async findFirstCollectionForUser(user: User) {
static async findFirstCollectionForUser(
user: User,
options: FindOptions = {}
) {
const id = await user.collectionIds();
return this.findOne({
where: {
teamId: user.teamId,
id,
},
order: [
@@ -439,6 +444,7 @@ class Collection extends ParanoidModel<
Sequelize.literal('"collection"."index" collate "C"'),
["updatedAt", "DESC"],
],
...options,
});
}
+5
View File
@@ -5,6 +5,7 @@ import {
ForeignKey,
BelongsTo,
Table,
Length,
} from "sequelize-typescript";
import Collection from "./Collection";
import Document from "./Document";
@@ -19,6 +20,10 @@ class Pin extends IdModel<
InferAttributes<Pin>,
Partial<InferCreationAttributes<Pin>>
> {
@Length({
max: 256,
msg: `index must be 256 characters or less`,
})
@Column
index: string | null;
+5
View File
@@ -5,6 +5,7 @@ import {
BelongsTo,
ForeignKey,
Table,
Length,
} from "sequelize-typescript";
import Collection from "./Collection";
import Document from "./Document";
@@ -18,6 +19,10 @@ class Star extends IdModel<
InferAttributes<Star>,
Partial<InferCreationAttributes<Star>>
> {
@Length({
max: 256,
msg: `index must be 256 characters or less`,
})
@Column
index: string | null;
+5 -2
View File
@@ -14,9 +14,9 @@ import {
Table,
DataType,
Scopes,
AllowNull,
AfterCreate,
AfterUpdate,
Length,
} from "sequelize-typescript";
import { CollectionPermission, DocumentPermission } from "@shared/types";
import Collection from "./Collection";
@@ -73,7 +73,10 @@ class UserMembership extends IdModel<
permission: CollectionPermission | DocumentPermission;
/** The visible sort order in "shared with me" */
@AllowNull
@Length({
max: 256,
msg: `index must be 256 characters or less`,
})
@Column
index: string | null;
+7 -20
View File
@@ -60,29 +60,16 @@ router.post(
const { user } = ctx.state.auth;
authorize(user, "createCollection", user.team);
if (!index) {
const collections = await Collection.findAll({
where: {
teamId: user.teamId,
deletedAt: null,
},
attributes: ["id", "index", "updatedAt"],
limit: 1,
order: [
// using LC_COLLATE:"C" because we need byte order to drive the sorting
Sequelize.literal('"collection"."index" collate "C"'),
["updatedAt", "DESC"],
],
if (index) {
index = await removeIndexCollision(user.teamId, index, { transaction });
} else {
const first = await Collection.findFirstCollectionForUser(user, {
attributes: ["id", "index"],
transaction,
});
index = fractionalIndex(
null,
collections.length ? collections[0].index : null
);
index = fractionalIndex(null, first ? first.index : null);
}
index = await removeIndexCollision(user.teamId, index);
const collection = Collection.build({
name,
content: data,
@@ -891,7 +878,7 @@ router.post(
});
authorize(user, "move", collection);
index = await removeIndexCollision(user.teamId, index);
index = await removeIndexCollision(user.teamId, index, { transaction });
await collection.update(
{
index,
+6 -2
View File
@@ -1,16 +1,18 @@
import fractionalIndex from "fractional-index";
import { Op, Sequelize } from "sequelize";
import { Op, Sequelize, type FindOptions } from "sequelize";
import Collection from "@server/models/Collection";
/**
*
* @param teamId The team id whose collections has to be fetched
* @param index the index for which collision has to be checked
* @param options Additional options to be passed to the query
* @returns An index, if there is collision returns a new index otherwise the same index
*/
export default async function removeIndexCollision(
teamId: string,
index: string
index: string,
options: FindOptions = {}
) {
const collection = await Collection.findOne({
where: {
@@ -18,6 +20,7 @@ export default async function removeIndexCollision(
deletedAt: null,
index,
},
...options,
});
if (!collection) {
@@ -38,6 +41,7 @@ export default async function removeIndexCollision(
Sequelize.literal('"collection"."index" collate "C"'),
["updatedAt", "DESC"],
],
...options,
});
const nextCollectionIndex = nextCollection.length
? nextCollection[0].index