fix: User updates are not synced between clients (#6490)
* Add Model.changeset method to get minified changes since last update * fix: Handle arrays * Add changes column, types * test
This commit is contained in:
@@ -88,6 +88,7 @@ class WebsocketProvider extends React.Component<Props> {
|
|||||||
pins,
|
pins,
|
||||||
stars,
|
stars,
|
||||||
memberships,
|
memberships,
|
||||||
|
users,
|
||||||
userMemberships,
|
userMemberships,
|
||||||
policies,
|
policies,
|
||||||
comments,
|
comments,
|
||||||
@@ -509,6 +510,10 @@ class WebsocketProvider extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.socket.on("users.update", (event: PartialWithId<User>) => {
|
||||||
|
users.add(event);
|
||||||
|
});
|
||||||
|
|
||||||
this.socket.on("users.demote", async (event: PartialWithId<User>) => {
|
this.socket.on("users.demote", async (event: PartialWithId<User>) => {
|
||||||
if (event.id === auth.user?.id) {
|
if (event.id === auth.user?.id) {
|
||||||
documents.all.forEach((document) => policies.remove(document.id));
|
documents.all.forEach((document) => policies.remove(document.id));
|
||||||
|
|||||||
@@ -184,6 +184,8 @@ export default class DeliverWebhookTask extends BaseTask<Props> {
|
|||||||
await this.handleIntegrationEvent(subscription, event);
|
await this.handleIntegrationEvent(subscription, event);
|
||||||
return;
|
return;
|
||||||
case "teams.create":
|
case "teams.create":
|
||||||
|
case "teams.delete":
|
||||||
|
case "teams.destroy":
|
||||||
// Ignored
|
// Ignored
|
||||||
return;
|
return;
|
||||||
case "teams.update":
|
case "teams.update":
|
||||||
|
|||||||
14
server/migrations/20240204171556-add-event-changeset.js
Normal file
14
server/migrations/20240204171556-add-event-changeset.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
async up(queryInterface, Sequelize) {
|
||||||
|
await queryInterface.addColumn("events", "changes", {
|
||||||
|
type: Sequelize.JSONB,
|
||||||
|
allowNull: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
async down(queryInterface) {
|
||||||
|
await queryInterface.removeColumn("events", "changes");
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import type {
|
import type {
|
||||||
|
CreateOptions,
|
||||||
InferAttributes,
|
InferAttributes,
|
||||||
InferCreationAttributes,
|
InferCreationAttributes,
|
||||||
SaveOptions,
|
SaveOptions,
|
||||||
@@ -17,7 +18,7 @@ import {
|
|||||||
Length,
|
Length,
|
||||||
} from "sequelize-typescript";
|
} from "sequelize-typescript";
|
||||||
import { globalEventQueue } from "../queues";
|
import { globalEventQueue } from "../queues";
|
||||||
import { Event as TEvent } from "../types";
|
import { APIContext, Event as TEvent } from "../types";
|
||||||
import Collection from "./Collection";
|
import Collection from "./Collection";
|
||||||
import Document from "./Document";
|
import Document from "./Document";
|
||||||
import Team from "./Team";
|
import Team from "./Team";
|
||||||
@@ -35,20 +36,36 @@ class Event extends IdModel<
|
|||||||
@Column(DataType.UUID)
|
@Column(DataType.UUID)
|
||||||
modelId: string | null;
|
modelId: string | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the event.
|
||||||
|
*/
|
||||||
@Length({
|
@Length({
|
||||||
max: 255,
|
max: 255,
|
||||||
msg: "name must be 255 characters or less",
|
msg: "name must be 255 characters or less",
|
||||||
})
|
})
|
||||||
@Column
|
@Column(DataType.STRING)
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The originating IP address of the event.
|
||||||
|
*/
|
||||||
@IsIP
|
@IsIP
|
||||||
@Column
|
@Column
|
||||||
ip: string | null;
|
ip: string | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata associated with the event, previously used for storing some changed attributes.
|
||||||
|
*/
|
||||||
@Column(DataType.JSONB)
|
@Column(DataType.JSONB)
|
||||||
data: Record<string, any> | null;
|
data: Record<string, any> | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The changes made to the model – gradually moving to this column away from `data` which can be
|
||||||
|
* used for arbitrary data associated with the event.
|
||||||
|
*/
|
||||||
|
@Column(DataType.JSONB)
|
||||||
|
changes?: Record<string, any> | null;
|
||||||
|
|
||||||
// hooks
|
// hooks
|
||||||
|
|
||||||
@BeforeCreate
|
@BeforeCreate
|
||||||
@@ -132,6 +149,30 @@ class Event extends IdModel<
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and persist new event from request context
|
||||||
|
*
|
||||||
|
* @param ctx The request context to use
|
||||||
|
* @param attributes The event attributes
|
||||||
|
* @returns A promise resolving to the new event
|
||||||
|
*/
|
||||||
|
static createFromContext(
|
||||||
|
ctx: APIContext,
|
||||||
|
attributes: Omit<Partial<Event>, "ip" | "teamId" | "actorId"> = {},
|
||||||
|
options?: CreateOptions<InferAttributes<Event>>
|
||||||
|
) {
|
||||||
|
const { user } = ctx.state.auth;
|
||||||
|
return this.create(
|
||||||
|
{
|
||||||
|
...attributes,
|
||||||
|
actorId: user.id,
|
||||||
|
teamId: user.teamId,
|
||||||
|
ip: ctx.request.ip,
|
||||||
|
},
|
||||||
|
options
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static ACTIVITY_EVENTS: TEvent["name"][] = [
|
static ACTIVITY_EVENTS: TEvent["name"][] = [
|
||||||
"collections.create",
|
"collections.create",
|
||||||
"collections.delete",
|
"collections.delete",
|
||||||
|
|||||||
@@ -228,8 +228,11 @@ class Team extends ParanoidModel<
|
|||||||
if (!this.preferences) {
|
if (!this.preferences) {
|
||||||
this.preferences = {};
|
this.preferences = {};
|
||||||
}
|
}
|
||||||
this.preferences[preference] = value;
|
|
||||||
this.changed("preferences", true);
|
this.preferences = {
|
||||||
|
...this.preferences,
|
||||||
|
[preference]: value,
|
||||||
|
};
|
||||||
|
|
||||||
return this.preferences;
|
return this.preferences;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -290,8 +290,10 @@ class User extends ParanoidModel<
|
|||||||
type: NotificationEventType,
|
type: NotificationEventType,
|
||||||
value = true
|
value = true
|
||||||
) => {
|
) => {
|
||||||
this.notificationSettings[type] = value;
|
this.notificationSettings = {
|
||||||
this.changed("notificationSettings", true);
|
...this.notificationSettings,
|
||||||
|
[type]: value,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -318,8 +320,10 @@ class User extends ParanoidModel<
|
|||||||
}
|
}
|
||||||
const binary = value ? 1 : 0;
|
const binary = value ? 1 : 0;
|
||||||
if (this.flags[flag] !== binary) {
|
if (this.flags[flag] !== binary) {
|
||||||
this.flags[flag] = binary;
|
this.flags = {
|
||||||
this.changed("flags", true);
|
...this.flags,
|
||||||
|
[flag]: binary,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.flags;
|
return this.flags;
|
||||||
@@ -345,9 +349,10 @@ class User extends ParanoidModel<
|
|||||||
if (!this.flags) {
|
if (!this.flags) {
|
||||||
this.flags = {};
|
this.flags = {};
|
||||||
}
|
}
|
||||||
this.flags[flag] = (this.flags[flag] ?? 0) + value;
|
this.flags = {
|
||||||
this.changed("flags", true);
|
...this.flags,
|
||||||
|
[flag]: (this.flags[flag] ?? 0) + value,
|
||||||
|
};
|
||||||
return this.flags;
|
return this.flags;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -362,9 +367,10 @@ class User extends ParanoidModel<
|
|||||||
if (!this.preferences) {
|
if (!this.preferences) {
|
||||||
this.preferences = {};
|
this.preferences = {};
|
||||||
}
|
}
|
||||||
this.preferences[preference] = value;
|
this.preferences = {
|
||||||
this.changed("preferences", true);
|
...this.preferences,
|
||||||
|
[preference]: value,
|
||||||
|
};
|
||||||
return this.preferences;
|
return this.preferences;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
52
server/models/base/Model.test.ts
Normal file
52
server/models/base/Model.test.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { v4 as uuid } from "uuid";
|
||||||
|
import { TeamPreference } from "@shared/types";
|
||||||
|
import { buildDocument, buildTeam } from "@server/test/factories";
|
||||||
|
|
||||||
|
describe("Model", () => {
|
||||||
|
describe("changeset", () => {
|
||||||
|
it("should return attributes changed since last save", async () => {
|
||||||
|
const team = await buildTeam({
|
||||||
|
name: "Test Team",
|
||||||
|
});
|
||||||
|
team.name = "New Name";
|
||||||
|
expect(Object.keys(team.changeset.attributes).length).toEqual(1);
|
||||||
|
expect(Object.keys(team.changeset.previous).length).toEqual(1);
|
||||||
|
expect(team.changeset.attributes.name).toEqual("New Name");
|
||||||
|
expect(team.changeset.previous.name).toEqual("Test Team");
|
||||||
|
|
||||||
|
await team.save();
|
||||||
|
expect(team.changeset.attributes).toEqual({});
|
||||||
|
expect(team.changeset.previous).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return partial of objects", async () => {
|
||||||
|
const team = await buildTeam();
|
||||||
|
team.setPreference(TeamPreference.Commenting, false);
|
||||||
|
expect(team.changeset.attributes.preferences).toEqual({
|
||||||
|
commenting: false,
|
||||||
|
});
|
||||||
|
expect(team.changeset.previous.preferences).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return boolean values", async () => {
|
||||||
|
const team = await buildTeam({
|
||||||
|
guestSignin: false,
|
||||||
|
});
|
||||||
|
team.guestSignin = true;
|
||||||
|
expect(team.changeset.attributes.guestSignin).toEqual(true);
|
||||||
|
expect(team.changeset.previous.guestSignin).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return full array if value changed", async () => {
|
||||||
|
const collaboratorId = uuid();
|
||||||
|
const document = await buildDocument();
|
||||||
|
const prev = document.collaboratorIds;
|
||||||
|
|
||||||
|
document.collaboratorIds = [...document.collaboratorIds, collaboratorId];
|
||||||
|
expect(document.changeset.attributes.collaboratorIds).toEqual(
|
||||||
|
document.collaboratorIds
|
||||||
|
);
|
||||||
|
expect(document.changeset.previous.collaboratorIds).toEqual(prev);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
/* eslint-disable @typescript-eslint/ban-types */
|
/* eslint-disable @typescript-eslint/ban-types */
|
||||||
import { FindOptions } from "sequelize";
|
import isArray from "lodash/isArray";
|
||||||
|
import isEqual from "lodash/isEqual";
|
||||||
|
import isObject from "lodash/isObject";
|
||||||
|
import pick from "lodash/pick";
|
||||||
|
import { FindOptions, NonAttribute } from "sequelize";
|
||||||
import { Model as SequelizeModel } from "sequelize-typescript";
|
import { Model as SequelizeModel } from "sequelize-typescript";
|
||||||
|
|
||||||
class Model<
|
class Model<
|
||||||
@@ -31,6 +35,60 @@ class Model<
|
|||||||
query.offset += query.limit;
|
query.offset += query.limit;
|
||||||
} while (results.length >= query.limit);
|
} while (results.length >= query.limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the attributes that have changed since the last save and their previous values.
|
||||||
|
*
|
||||||
|
* @returns An object with `attributes` and `previousAttributes` keys.
|
||||||
|
*/
|
||||||
|
public get changeset(): NonAttribute<{
|
||||||
|
attributes: Partial<TModelAttributes>;
|
||||||
|
previous: Partial<TModelAttributes>;
|
||||||
|
}> {
|
||||||
|
const changes = this.changed() as Array<keyof TModelAttributes> | false;
|
||||||
|
const attributes: Partial<TModelAttributes> = {};
|
||||||
|
const previousAttributes: Partial<TModelAttributes> = {};
|
||||||
|
|
||||||
|
if (!changes) {
|
||||||
|
return {
|
||||||
|
attributes,
|
||||||
|
previous: previousAttributes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const change of changes) {
|
||||||
|
const previous = this.previous(change);
|
||||||
|
const current = this.getDataValue(change);
|
||||||
|
|
||||||
|
if (
|
||||||
|
isObject(previous) &&
|
||||||
|
isObject(current) &&
|
||||||
|
!isArray(previous) &&
|
||||||
|
!isArray(current)
|
||||||
|
) {
|
||||||
|
const difference = Object.keys(previous)
|
||||||
|
.concat(Object.keys(current))
|
||||||
|
.filter((key) => !isEqual(previous[key], current[key]));
|
||||||
|
|
||||||
|
previousAttributes[change] = pick(
|
||||||
|
previous,
|
||||||
|
difference
|
||||||
|
) as TModelAttributes[keyof TModelAttributes];
|
||||||
|
attributes[change] = pick(
|
||||||
|
current,
|
||||||
|
difference
|
||||||
|
) as TModelAttributes[keyof TModelAttributes];
|
||||||
|
} else {
|
||||||
|
previousAttributes[change] = previous;
|
||||||
|
attributes[change] = current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
attributes,
|
||||||
|
previous: previousAttributes,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Model;
|
export default Model;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
Subscription,
|
Subscription,
|
||||||
Notification,
|
Notification,
|
||||||
UserMembership,
|
UserMembership,
|
||||||
|
User,
|
||||||
} from "@server/models";
|
} from "@server/models";
|
||||||
import {
|
import {
|
||||||
presentComment,
|
presentComment,
|
||||||
@@ -27,6 +28,7 @@ import {
|
|||||||
presentSubscription,
|
presentSubscription,
|
||||||
presentTeam,
|
presentTeam,
|
||||||
presentMembership,
|
presentMembership,
|
||||||
|
presentUser,
|
||||||
} from "@server/presenters";
|
} from "@server/presenters";
|
||||||
import presentNotification from "@server/presenters/notification";
|
import presentNotification from "@server/presenters/notification";
|
||||||
import { Event } from "../../types";
|
import { Event } from "../../types";
|
||||||
@@ -667,6 +669,19 @@ export default class WebsocketsProcessor {
|
|||||||
.emit(event.name, presentTeam(team));
|
.emit(event.name, presentTeam(team));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "users.update": {
|
||||||
|
const user = await User.findByPk(event.userId);
|
||||||
|
if (!user) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
socketio
|
||||||
|
.to(`user-${event.userId}`)
|
||||||
|
.emit(event.name, presentUser(user, { includeDetails: true }));
|
||||||
|
|
||||||
|
socketio.to(`team-${user.teamId}`).emit(event.name, presentUser(user));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
case "users.demote": {
|
case "users.demote": {
|
||||||
return socketio
|
return socketio
|
||||||
.to(`user-${event.userId}`)
|
.to(`user-${event.userId}`)
|
||||||
|
|||||||
@@ -226,17 +226,17 @@ router.post(
|
|||||||
user.setPreference(key, preferences[key] as boolean);
|
user.setPreference(key, preferences[key] as boolean);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await user.save({ transaction });
|
|
||||||
await Event.create(
|
await Event.createFromContext(
|
||||||
|
ctx,
|
||||||
{
|
{
|
||||||
name: "users.update",
|
name: "users.update",
|
||||||
actorId: user.id,
|
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
teamId: user.teamId,
|
changes: user.changeset,
|
||||||
ip: ctx.request.ip,
|
|
||||||
},
|
},
|
||||||
{ transaction }
|
{ transaction }
|
||||||
);
|
);
|
||||||
|
await user.save({ transaction });
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
data: presentUser(user, {
|
data: presentUser(user, {
|
||||||
@@ -547,9 +547,18 @@ router.post(
|
|||||||
async (ctx: APIContext<T.UsersNotificationsSubscribeReq>) => {
|
async (ctx: APIContext<T.UsersNotificationsSubscribeReq>) => {
|
||||||
const { eventType } = ctx.input.body;
|
const { eventType } = ctx.input.body;
|
||||||
const { transaction } = ctx.state;
|
const { transaction } = ctx.state;
|
||||||
|
|
||||||
const { user } = ctx.state.auth;
|
const { user } = ctx.state.auth;
|
||||||
user.setNotificationEventType(eventType, true);
|
user.setNotificationEventType(eventType, true);
|
||||||
|
|
||||||
|
await Event.createFromContext(
|
||||||
|
ctx,
|
||||||
|
{
|
||||||
|
name: "users.update",
|
||||||
|
userId: user.id,
|
||||||
|
changes: user.changeset,
|
||||||
|
},
|
||||||
|
{ transaction }
|
||||||
|
);
|
||||||
await user.save({ transaction });
|
await user.save({ transaction });
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
@@ -566,9 +575,18 @@ router.post(
|
|||||||
async (ctx: APIContext<T.UsersNotificationsUnsubscribeReq>) => {
|
async (ctx: APIContext<T.UsersNotificationsUnsubscribeReq>) => {
|
||||||
const { eventType } = ctx.input.body;
|
const { eventType } = ctx.input.body;
|
||||||
const { transaction } = ctx.state;
|
const { transaction } = ctx.state;
|
||||||
|
|
||||||
const { user } = ctx.state.auth;
|
const { user } = ctx.state.auth;
|
||||||
user.setNotificationEventType(eventType, false);
|
user.setNotificationEventType(eventType, false);
|
||||||
|
|
||||||
|
await Event.createFromContext(
|
||||||
|
ctx,
|
||||||
|
{
|
||||||
|
name: "users.update",
|
||||||
|
userId: user.id,
|
||||||
|
changes: user.changeset,
|
||||||
|
},
|
||||||
|
{ transaction }
|
||||||
|
);
|
||||||
await user.save({ transaction });
|
await user.save({ transaction });
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ParameterizedContext, DefaultContext } from "koa";
|
import { ParameterizedContext, DefaultContext } from "koa";
|
||||||
import { IRouterParamContext } from "koa-router";
|
import { IRouterParamContext } from "koa-router";
|
||||||
import { Transaction } from "sequelize";
|
import { InferAttributes, Model, Transaction } from "sequelize";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
CollectionSort,
|
CollectionSort,
|
||||||
@@ -11,7 +11,29 @@ import {
|
|||||||
} from "@shared/types";
|
} from "@shared/types";
|
||||||
import { BaseSchema } from "@server/routes/api/schema";
|
import { BaseSchema } from "@server/routes/api/schema";
|
||||||
import { AccountProvisionerResult } from "./commands/accountProvisioner";
|
import { AccountProvisionerResult } from "./commands/accountProvisioner";
|
||||||
import { FileOperation, Team, User } from "./models";
|
import type {
|
||||||
|
ApiKey,
|
||||||
|
Attachment,
|
||||||
|
AuthenticationProvider,
|
||||||
|
FileOperation,
|
||||||
|
Revision,
|
||||||
|
Team,
|
||||||
|
User,
|
||||||
|
UserMembership,
|
||||||
|
WebhookSubscription,
|
||||||
|
Pin,
|
||||||
|
Star,
|
||||||
|
Document,
|
||||||
|
Collection,
|
||||||
|
Group,
|
||||||
|
Integration,
|
||||||
|
Comment,
|
||||||
|
Subscription,
|
||||||
|
View,
|
||||||
|
Notification,
|
||||||
|
Share,
|
||||||
|
GroupPermission,
|
||||||
|
} from "./models";
|
||||||
|
|
||||||
export enum AuthenticationType {
|
export enum AuthenticationType {
|
||||||
API = "api",
|
API = "api",
|
||||||
@@ -56,13 +78,17 @@ export interface APIContext<ReqT = BaseReq, ResT = BaseRes>
|
|||||||
input: ReqT;
|
input: ReqT;
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaseEvent = {
|
type BaseEvent<T extends Model> = {
|
||||||
teamId: string;
|
teamId: string;
|
||||||
actorId: string;
|
actorId: string;
|
||||||
ip: string;
|
ip: string;
|
||||||
|
changes?: {
|
||||||
|
attributes: Partial<InferAttributes<T>>;
|
||||||
|
previous: Partial<InferAttributes<T>>;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ApiKeyEvent = BaseEvent & {
|
export type ApiKeyEvent = BaseEvent<ApiKey> & {
|
||||||
name: "api_keys.create" | "api_keys.delete";
|
name: "api_keys.create" | "api_keys.delete";
|
||||||
modelId: string;
|
modelId: string;
|
||||||
data: {
|
data: {
|
||||||
@@ -70,7 +96,7 @@ export type ApiKeyEvent = BaseEvent & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AttachmentEvent = BaseEvent &
|
export type AttachmentEvent = BaseEvent<Attachment> &
|
||||||
(
|
(
|
||||||
| {
|
| {
|
||||||
name: "attachments.create";
|
name: "attachments.create";
|
||||||
@@ -89,7 +115,7 @@ export type AttachmentEvent = BaseEvent &
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export type AuthenticationProviderEvent = BaseEvent & {
|
export type AuthenticationProviderEvent = BaseEvent<AuthenticationProvider> & {
|
||||||
name: "authenticationProviders.update";
|
name: "authenticationProviders.update";
|
||||||
modelId: string;
|
modelId: string;
|
||||||
data: {
|
data: {
|
||||||
@@ -97,7 +123,7 @@ export type AuthenticationProviderEvent = BaseEvent & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UserEvent = BaseEvent &
|
export type UserEvent = BaseEvent<User> &
|
||||||
(
|
(
|
||||||
| {
|
| {
|
||||||
name:
|
name:
|
||||||
@@ -126,7 +152,7 @@ export type UserEvent = BaseEvent &
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export type UserMembershipEvent = BaseEvent & {
|
export type UserMembershipEvent = BaseEvent<UserMembership> & {
|
||||||
name: "userMemberships.update";
|
name: "userMemberships.update";
|
||||||
modelId: string;
|
modelId: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
@@ -136,9 +162,8 @@ export type UserMembershipEvent = BaseEvent & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DocumentEvent = BaseEvent &
|
export type DocumentEvent = BaseEvent<Document> &
|
||||||
(
|
(
|
||||||
| DocumentUserEvent
|
|
||||||
| {
|
| {
|
||||||
name:
|
name:
|
||||||
| "documents.create"
|
| "documents.create"
|
||||||
@@ -191,14 +216,14 @@ export type DocumentEvent = BaseEvent &
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export type RevisionEvent = BaseEvent & {
|
export type RevisionEvent = BaseEvent<Revision> & {
|
||||||
name: "revisions.create";
|
name: "revisions.create";
|
||||||
documentId: string;
|
documentId: string;
|
||||||
collectionId: string;
|
collectionId: string;
|
||||||
modelId: string;
|
modelId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FileOperationEvent = BaseEvent & {
|
export type FileOperationEvent = BaseEvent<FileOperation> & {
|
||||||
name:
|
name:
|
||||||
| "fileOperations.create"
|
| "fileOperations.create"
|
||||||
| "fileOperations.update"
|
| "fileOperations.update"
|
||||||
@@ -207,7 +232,7 @@ export type FileOperationEvent = BaseEvent & {
|
|||||||
data: Partial<FileOperation>;
|
data: Partial<FileOperation>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CollectionUserEvent = BaseEvent & {
|
export type CollectionUserEvent = BaseEvent<UserMembership> & {
|
||||||
name: "collections.add_user" | "collections.remove_user";
|
name: "collections.add_user" | "collections.remove_user";
|
||||||
userId: string;
|
userId: string;
|
||||||
modelId: string;
|
modelId: string;
|
||||||
@@ -218,14 +243,14 @@ export type CollectionUserEvent = BaseEvent & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CollectionGroupEvent = BaseEvent & {
|
export type CollectionGroupEvent = BaseEvent<GroupPermission> & {
|
||||||
name: "collections.add_group" | "collections.remove_group";
|
name: "collections.add_group" | "collections.remove_group";
|
||||||
collectionId: string;
|
collectionId: string;
|
||||||
modelId: string;
|
modelId: string;
|
||||||
data: { name: string };
|
data: { name: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DocumentUserEvent = BaseEvent & {
|
export type DocumentUserEvent = BaseEvent<UserMembership> & {
|
||||||
name: "documents.add_user" | "documents.remove_user";
|
name: "documents.add_user" | "documents.remove_user";
|
||||||
userId: string;
|
userId: string;
|
||||||
modelId: string;
|
modelId: string;
|
||||||
@@ -237,10 +262,8 @@ export type DocumentUserEvent = BaseEvent & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CollectionEvent = BaseEvent &
|
export type CollectionEvent = BaseEvent<Collection> &
|
||||||
(
|
(
|
||||||
| CollectionUserEvent
|
|
||||||
| CollectionGroupEvent
|
|
||||||
| {
|
| {
|
||||||
name: "collections.create";
|
name: "collections.create";
|
||||||
collectionId: string;
|
collectionId: string;
|
||||||
@@ -273,7 +296,7 @@ export type CollectionEvent = BaseEvent &
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export type GroupUserEvent = BaseEvent & {
|
export type GroupUserEvent = BaseEvent<UserMembership> & {
|
||||||
name: "groups.add_user" | "groups.remove_user";
|
name: "groups.add_user" | "groups.remove_user";
|
||||||
userId: string;
|
userId: string;
|
||||||
modelId: string;
|
modelId: string;
|
||||||
@@ -282,7 +305,7 @@ export type GroupUserEvent = BaseEvent & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GroupEvent = BaseEvent &
|
export type GroupEvent = BaseEvent<Group> &
|
||||||
(
|
(
|
||||||
| GroupUserEvent
|
| GroupUserEvent
|
||||||
| {
|
| {
|
||||||
@@ -294,24 +317,24 @@ export type GroupEvent = BaseEvent &
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export type IntegrationEvent = BaseEvent & {
|
export type IntegrationEvent = BaseEvent<Integration> & {
|
||||||
name: "integrations.create" | "integrations.update" | "integrations.delete";
|
name: "integrations.create" | "integrations.update" | "integrations.delete";
|
||||||
modelId: string;
|
modelId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TeamEvent = BaseEvent & {
|
export type TeamEvent = BaseEvent<Team> & {
|
||||||
name: "teams.create" | "teams.update";
|
name: "teams.create" | "teams.update" | "teams.delete" | "teams.destroy";
|
||||||
data: Partial<Team>;
|
data: Partial<Team>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PinEvent = BaseEvent & {
|
export type PinEvent = BaseEvent<Pin> & {
|
||||||
name: "pins.create" | "pins.update" | "pins.delete";
|
name: "pins.create" | "pins.update" | "pins.delete";
|
||||||
modelId: string;
|
modelId: string;
|
||||||
documentId: string;
|
documentId: string;
|
||||||
collectionId?: string;
|
collectionId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CommentUpdateEvent = BaseEvent & {
|
export type CommentUpdateEvent = BaseEvent<Comment> & {
|
||||||
name: "comments.update";
|
name: "comments.update";
|
||||||
modelId: string;
|
modelId: string;
|
||||||
documentId: string;
|
documentId: string;
|
||||||
@@ -322,14 +345,14 @@ export type CommentUpdateEvent = BaseEvent & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type CommentEvent =
|
export type CommentEvent =
|
||||||
| (BaseEvent & {
|
| (BaseEvent<Comment> & {
|
||||||
name: "comments.create";
|
name: "comments.create";
|
||||||
modelId: string;
|
modelId: string;
|
||||||
documentId: string;
|
documentId: string;
|
||||||
actorId: string;
|
actorId: string;
|
||||||
})
|
})
|
||||||
| CommentUpdateEvent
|
| CommentUpdateEvent
|
||||||
| (BaseEvent & {
|
| (BaseEvent<Comment> & {
|
||||||
name: "comments.delete";
|
name: "comments.delete";
|
||||||
modelId: string;
|
modelId: string;
|
||||||
documentId: string;
|
documentId: string;
|
||||||
@@ -337,14 +360,14 @@ export type CommentEvent =
|
|||||||
collectionId: string;
|
collectionId: string;
|
||||||
});
|
});
|
||||||
|
|
||||||
export type StarEvent = BaseEvent & {
|
export type StarEvent = BaseEvent<Star> & {
|
||||||
name: "stars.create" | "stars.update" | "stars.delete";
|
name: "stars.create" | "stars.update" | "stars.delete";
|
||||||
modelId: string;
|
modelId: string;
|
||||||
documentId: string;
|
documentId: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ShareEvent = BaseEvent & {
|
export type ShareEvent = BaseEvent<Share> & {
|
||||||
name: "shares.create" | "shares.update" | "shares.revoke";
|
name: "shares.create" | "shares.update" | "shares.revoke";
|
||||||
modelId: string;
|
modelId: string;
|
||||||
documentId: string;
|
documentId: string;
|
||||||
@@ -354,14 +377,14 @@ export type ShareEvent = BaseEvent & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SubscriptionEvent = BaseEvent & {
|
export type SubscriptionEvent = BaseEvent<Subscription> & {
|
||||||
name: "subscriptions.create" | "subscriptions.delete";
|
name: "subscriptions.create" | "subscriptions.delete";
|
||||||
modelId: string;
|
modelId: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
documentId: string | null;
|
documentId: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ViewEvent = BaseEvent & {
|
export type ViewEvent = BaseEvent<View> & {
|
||||||
name: "views.create";
|
name: "views.create";
|
||||||
documentId: string;
|
documentId: string;
|
||||||
collectionId: string;
|
collectionId: string;
|
||||||
@@ -373,7 +396,7 @@ export type ViewEvent = BaseEvent & {
|
|||||||
|
|
||||||
export type WebhookDeliveryStatus = "pending" | "success" | "failed";
|
export type WebhookDeliveryStatus = "pending" | "success" | "failed";
|
||||||
|
|
||||||
export type WebhookSubscriptionEvent = BaseEvent & {
|
export type WebhookSubscriptionEvent = BaseEvent<WebhookSubscription> & {
|
||||||
name:
|
name:
|
||||||
| "webhookSubscriptions.create"
|
| "webhookSubscriptions.create"
|
||||||
| "webhookSubscriptions.delete"
|
| "webhookSubscriptions.delete"
|
||||||
@@ -386,7 +409,7 @@ export type WebhookSubscriptionEvent = BaseEvent & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NotificationEvent = BaseEvent & {
|
export type NotificationEvent = BaseEvent<Notification> & {
|
||||||
name: "notifications.create" | "notifications.update";
|
name: "notifications.create" | "notifications.update";
|
||||||
modelId: string;
|
modelId: string;
|
||||||
teamId: string;
|
teamId: string;
|
||||||
@@ -402,10 +425,13 @@ export type Event =
|
|||||||
| AttachmentEvent
|
| AttachmentEvent
|
||||||
| AuthenticationProviderEvent
|
| AuthenticationProviderEvent
|
||||||
| DocumentEvent
|
| DocumentEvent
|
||||||
|
| DocumentUserEvent
|
||||||
| PinEvent
|
| PinEvent
|
||||||
| CommentEvent
|
| CommentEvent
|
||||||
| StarEvent
|
| StarEvent
|
||||||
| CollectionEvent
|
| CollectionEvent
|
||||||
|
| CollectionUserEvent
|
||||||
|
| CollectionGroupEvent
|
||||||
| FileOperationEvent
|
| FileOperationEvent
|
||||||
| IntegrationEvent
|
| IntegrationEvent
|
||||||
| GroupEvent
|
| GroupEvent
|
||||||
|
|||||||
Reference in New Issue
Block a user