mirror of
https://github.com/outline/outline.git
synced 2025-12-21 10:39:41 -06:00
Convert pin mutations to use auto event insertion (#7993)
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
import { Event } from "@server/models";
|
||||
import { buildDocument, buildUser } from "@server/test/factories";
|
||||
import { withAPIContext } from "@server/test/support";
|
||||
import pinCreator from "./pinCreator";
|
||||
|
||||
describe("pinCreator", () => {
|
||||
const ip = "127.0.0.1";
|
||||
|
||||
it("should create pin to home", async () => {
|
||||
const user = await buildUser();
|
||||
const document = await buildDocument({
|
||||
@@ -12,11 +11,13 @@ describe("pinCreator", () => {
|
||||
teamId: user.teamId,
|
||||
});
|
||||
|
||||
const pin = await pinCreator({
|
||||
documentId: document.id,
|
||||
user,
|
||||
ip,
|
||||
});
|
||||
const pin = await withAPIContext(user, (ctx) =>
|
||||
pinCreator({
|
||||
ctx,
|
||||
user,
|
||||
documentId: document.id,
|
||||
})
|
||||
);
|
||||
|
||||
const event = await Event.findLatest({
|
||||
teamId: user.teamId,
|
||||
@@ -36,12 +37,14 @@ describe("pinCreator", () => {
|
||||
teamId: user.teamId,
|
||||
});
|
||||
|
||||
const pin = await pinCreator({
|
||||
documentId: document.id,
|
||||
collectionId: document.collectionId,
|
||||
user,
|
||||
ip,
|
||||
});
|
||||
const pin = await withAPIContext(user, (ctx) =>
|
||||
pinCreator({
|
||||
ctx,
|
||||
user,
|
||||
documentId: document.id,
|
||||
collectionId: document.collectionId,
|
||||
})
|
||||
);
|
||||
|
||||
const event = await Event.findLatest({
|
||||
teamId: user.teamId,
|
||||
|
||||
@@ -2,10 +2,12 @@ import fractionalIndex from "fractional-index";
|
||||
import { Sequelize, Op, WhereOptions } from "sequelize";
|
||||
import { PinValidation } from "@shared/validations";
|
||||
import { ValidationError } from "@server/errors";
|
||||
import { Pin, User, Event } from "@server/models";
|
||||
import { sequelize } from "@server/storage/database";
|
||||
import { Pin, User } from "@server/models";
|
||||
import { APIContext } from "@server/types";
|
||||
|
||||
type Props = {
|
||||
/** The request context */
|
||||
ctx: APIContext;
|
||||
/** The user creating the pin */
|
||||
user: User;
|
||||
/** The document to pin */
|
||||
@@ -14,8 +16,6 @@ type Props = {
|
||||
collectionId?: string | null;
|
||||
/** The index to pin the document at. If no index is provided then it will be pinned to the end of the collection */
|
||||
index?: string;
|
||||
/** The IP address of the user creating the pin */
|
||||
ip: string;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -26,10 +26,10 @@ type Props = {
|
||||
* @returns Pin The pin that was created
|
||||
*/
|
||||
export default async function pinCreator({
|
||||
ctx,
|
||||
user,
|
||||
documentId,
|
||||
collectionId,
|
||||
ip,
|
||||
...rest
|
||||
}: Props): Promise<Pin> {
|
||||
let { index } = rest;
|
||||
@@ -62,38 +62,13 @@ export default async function pinCreator({
|
||||
index = fractionalIndex(pins.length ? pins[0].index : null, null);
|
||||
}
|
||||
|
||||
const transaction = await sequelize.transaction();
|
||||
let pin;
|
||||
|
||||
try {
|
||||
pin = await Pin.create(
|
||||
{
|
||||
createdById: user.id,
|
||||
teamId: user.teamId,
|
||||
collectionId,
|
||||
documentId,
|
||||
index,
|
||||
},
|
||||
{ transaction }
|
||||
);
|
||||
|
||||
await Event.create(
|
||||
{
|
||||
name: "pins.create",
|
||||
modelId: pin.id,
|
||||
teamId: user.teamId,
|
||||
actorId: user.id,
|
||||
documentId,
|
||||
collectionId,
|
||||
ip,
|
||||
},
|
||||
{ transaction }
|
||||
);
|
||||
await transaction.commit();
|
||||
} catch (err) {
|
||||
await transaction.rollback();
|
||||
throw err;
|
||||
}
|
||||
const pin = await Pin.createWithCtx(ctx, {
|
||||
createdById: user.id,
|
||||
teamId: user.teamId,
|
||||
collectionId,
|
||||
documentId,
|
||||
index,
|
||||
});
|
||||
|
||||
return pin;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ type Props = {
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated use pin.destroyWithCtx instead. This will be removed once document routes migrate to auto event insertion using APIContext.
|
||||
*
|
||||
* This command destroys a document pin. This just removes the pin itself and
|
||||
* does not touch the document
|
||||
*
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import { Event, Pin, User } from "@server/models";
|
||||
import { sequelize } from "@server/storage/database";
|
||||
|
||||
type Props = {
|
||||
/** The user updating the pin */
|
||||
user: User;
|
||||
/** The existing pin */
|
||||
pin: Pin;
|
||||
/** The index to pin the document at */
|
||||
index: string;
|
||||
/** The IP address of the user creating the pin */
|
||||
ip: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* This command updates a "pinned" document. A pin can only be moved to a new
|
||||
* index (reordered) once created.
|
||||
*
|
||||
* @param Props The properties of the pin to update
|
||||
* @returns Pin The updated pin
|
||||
*/
|
||||
export default async function pinUpdater({
|
||||
user,
|
||||
pin,
|
||||
index,
|
||||
ip,
|
||||
}: Props): Promise<Pin> {
|
||||
const transaction = await sequelize.transaction();
|
||||
|
||||
try {
|
||||
pin.index = index;
|
||||
await pin.save({ transaction });
|
||||
|
||||
await Event.create(
|
||||
{
|
||||
name: "pins.update",
|
||||
modelId: pin.id,
|
||||
teamId: user.teamId,
|
||||
actorId: user.id,
|
||||
documentId: pin.documentId,
|
||||
collectionId: pin.collectionId,
|
||||
ip,
|
||||
},
|
||||
{ transaction }
|
||||
);
|
||||
await transaction.commit();
|
||||
} catch (err) {
|
||||
await transaction.rollback();
|
||||
throw err;
|
||||
}
|
||||
|
||||
return pin;
|
||||
}
|
||||
@@ -20,6 +20,8 @@ class Pin extends IdModel<
|
||||
InferAttributes<Pin>,
|
||||
Partial<InferCreationAttributes<Pin>>
|
||||
> {
|
||||
static eventNamespace = "pins";
|
||||
|
||||
@Length({
|
||||
max: 256,
|
||||
msg: `index must be 256 characters or less`,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import Router from "koa-router";
|
||||
import { Sequelize, Op } from "sequelize";
|
||||
import { Sequelize, Op, Transaction } from "sequelize";
|
||||
import pinCreator from "@server/commands/pinCreator";
|
||||
import pinDestroyer from "@server/commands/pinDestroyer";
|
||||
import pinUpdater from "@server/commands/pinUpdater";
|
||||
import auth from "@server/middlewares/authentication";
|
||||
import { transaction } from "@server/middlewares/transaction";
|
||||
import validate from "@server/middlewares/validate";
|
||||
import { Collection, Document, Pin } from "@server/models";
|
||||
import { authorize } from "@server/policies";
|
||||
@@ -22,18 +21,21 @@ router.post(
|
||||
"pins.create",
|
||||
auth(),
|
||||
validate(T.PinsCreateSchema),
|
||||
transaction(),
|
||||
async (ctx: APIContext<T.PinsCreateReq>) => {
|
||||
const { documentId, collectionId, index } = ctx.input.body;
|
||||
const { user } = ctx.state.auth;
|
||||
const { transaction } = ctx.state;
|
||||
const document = await Document.findByPk(documentId, {
|
||||
userId: user.id,
|
||||
transaction,
|
||||
});
|
||||
authorize(user, "read", document);
|
||||
|
||||
if (collectionId) {
|
||||
const collection = await Collection.scope({
|
||||
method: ["withMembership", user.id],
|
||||
}).findByPk(collectionId);
|
||||
}).findByPk(collectionId, { transaction });
|
||||
authorize(user, "update", collection);
|
||||
authorize(user, "pin", document);
|
||||
} else {
|
||||
@@ -41,10 +43,10 @@ router.post(
|
||||
}
|
||||
|
||||
const pin = await pinCreator({
|
||||
ctx,
|
||||
user,
|
||||
documentId,
|
||||
collectionId,
|
||||
ip: ctx.request.ip,
|
||||
index,
|
||||
});
|
||||
|
||||
@@ -108,13 +110,20 @@ router.post(
|
||||
"pins.update",
|
||||
auth(),
|
||||
validate(T.PinsUpdateSchema),
|
||||
transaction(),
|
||||
async (ctx: APIContext<T.PinsUpdateReq>) => {
|
||||
const { id, index } = ctx.input.body;
|
||||
const { user } = ctx.state.auth;
|
||||
let pin = await Pin.findByPk(id, { rejectOnEmpty: true });
|
||||
const { transaction } = ctx.state;
|
||||
const pin = await Pin.findByPk(id, {
|
||||
transaction,
|
||||
lock: Transaction.LOCK.UPDATE,
|
||||
rejectOnEmpty: true,
|
||||
});
|
||||
|
||||
const document = await Document.findByPk(pin.documentId, {
|
||||
userId: user.id,
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (pin.collectionId) {
|
||||
@@ -123,12 +132,7 @@ router.post(
|
||||
authorize(user, "update", pin);
|
||||
}
|
||||
|
||||
pin = await pinUpdater({
|
||||
user,
|
||||
pin,
|
||||
ip: ctx.request.ip,
|
||||
index,
|
||||
});
|
||||
await pin.updateWithCtx(ctx, { index });
|
||||
|
||||
ctx.body = {
|
||||
data: presentPin(pin),
|
||||
@@ -141,14 +145,21 @@ router.post(
|
||||
"pins.delete",
|
||||
auth(),
|
||||
validate(T.PinsDeleteSchema),
|
||||
transaction(),
|
||||
async (ctx: APIContext<T.PinsDeleteReq>) => {
|
||||
const { id } = ctx.input.body;
|
||||
const { transaction } = ctx.state;
|
||||
|
||||
const { user } = ctx.state.auth;
|
||||
const pin = await Pin.findByPk(id, { rejectOnEmpty: true });
|
||||
const pin = await Pin.findByPk(id, {
|
||||
transaction,
|
||||
lock: Transaction.LOCK.UPDATE,
|
||||
rejectOnEmpty: true,
|
||||
});
|
||||
|
||||
const document = await Document.findByPk(pin.documentId, {
|
||||
userId: user.id,
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (pin.collectionId) {
|
||||
@@ -157,7 +168,7 @@ router.post(
|
||||
authorize(user, "delete", pin);
|
||||
}
|
||||
|
||||
await pinDestroyer({ user, pin, ip: ctx.request.ip });
|
||||
await pin.destroyWithCtx(ctx);
|
||||
|
||||
ctx.body = {
|
||||
success: true,
|
||||
|
||||
Reference in New Issue
Block a user