mirror of
https://github.com/outline/outline.git
synced 2026-01-06 02:59:54 -06:00
196 lines
4.8 KiB
TypeScript
196 lines
4.8 KiB
TypeScript
import type {
|
||
CreateOptions,
|
||
InferAttributes,
|
||
InferCreationAttributes,
|
||
SaveOptions,
|
||
WhereOptions,
|
||
} from "sequelize";
|
||
import {
|
||
ForeignKey,
|
||
AfterSave,
|
||
BeforeCreate,
|
||
BelongsTo,
|
||
Column,
|
||
IsIP,
|
||
IsUUID,
|
||
Table,
|
||
DataType,
|
||
Length,
|
||
} from "sequelize-typescript";
|
||
import { globalEventQueue } from "../queues";
|
||
import type { APIContext } from "../types";
|
||
import { AuthenticationType } from "../types";
|
||
import Collection from "./Collection";
|
||
import Document from "./Document";
|
||
import Team from "./Team";
|
||
import User from "./User";
|
||
import IdModel from "./base/IdModel";
|
||
import Fix from "./decorators/Fix";
|
||
import type { Context } from "koa";
|
||
|
||
@Table({ tableName: "events", modelName: "event", updatedAt: false })
|
||
@Fix
|
||
class Event extends IdModel<
|
||
InferAttributes<Event>,
|
||
Partial<InferCreationAttributes<Event>>
|
||
> {
|
||
@IsUUID(4)
|
||
@Column(DataType.UUID)
|
||
modelId: string | null;
|
||
|
||
/** The name of the event. */
|
||
@Length({
|
||
max: 255,
|
||
msg: "name must be 255 characters or less",
|
||
})
|
||
@Column(DataType.STRING)
|
||
name: string;
|
||
|
||
/** The originating IP address of the event. */
|
||
@IsIP
|
||
@Column
|
||
ip: string | null;
|
||
|
||
/** The type of authentication used to create the event. */
|
||
@Column(DataType.ENUM(...Object.values(AuthenticationType)))
|
||
authType: AuthenticationType | null;
|
||
|
||
/**
|
||
* Metadata associated with the event, previously used for storing some changed attributes.
|
||
* Note that the `data` column will be visible to the client and API requests.
|
||
*/
|
||
@Column(DataType.JSONB)
|
||
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
|
||
|
||
@BeforeCreate
|
||
static cleanupIp(model: Event) {
|
||
if (model.ip) {
|
||
// cleanup IPV6 representations of IPV4 addresses
|
||
model.ip = model.ip.replace(/^::ffff:/, "");
|
||
}
|
||
}
|
||
|
||
@AfterSave
|
||
static async enqueue(
|
||
model: Event,
|
||
options: SaveOptions<InferAttributes<Event>>
|
||
) {
|
||
if (options.transaction) {
|
||
// 'findOrCreate' creates a new transaction always, and the transaction from the middleware is set as its parent.
|
||
// We want to use the parent transaction, otherwise the 'afterCommit' hook will never fire in this case.
|
||
// See: https://github.com/sequelize/sequelize/issues/17452
|
||
(options.transaction.parent || options.transaction).afterCommit(
|
||
() => void globalEventQueue().add(model)
|
||
);
|
||
return;
|
||
}
|
||
void globalEventQueue().add(model);
|
||
}
|
||
|
||
// associations
|
||
|
||
@BelongsTo(() => User, "userId")
|
||
user: User | null;
|
||
|
||
@ForeignKey(() => User)
|
||
@Column(DataType.UUID)
|
||
userId: string | null;
|
||
|
||
@BelongsTo(() => Document, "documentId")
|
||
document: Document | null;
|
||
|
||
@ForeignKey(() => Document)
|
||
@Column(DataType.UUID)
|
||
documentId: string | null;
|
||
|
||
@BelongsTo(() => User, "actorId")
|
||
actor: User;
|
||
|
||
@ForeignKey(() => User)
|
||
@Column(DataType.UUID)
|
||
actorId: string | null;
|
||
|
||
@BelongsTo(() => Collection, "collectionId")
|
||
collection: Collection | null;
|
||
|
||
@ForeignKey(() => Collection)
|
||
@Column(DataType.UUID)
|
||
collectionId: string | null;
|
||
|
||
@BelongsTo(() => Team, "teamId")
|
||
team: Team;
|
||
|
||
@ForeignKey(() => Team)
|
||
@Column(DataType.UUID)
|
||
teamId: string;
|
||
|
||
/*
|
||
* Schedule can be used to send events into the event system without recording
|
||
* them in the database or audit trail – consider using a task instead.
|
||
*/
|
||
static schedule(event: Partial<Event>) {
|
||
const now = new Date();
|
||
return globalEventQueue().add(
|
||
this.build({
|
||
createdAt: now,
|
||
...event,
|
||
})
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Find the latest event matching the where clause
|
||
*
|
||
* @param where The options to match against
|
||
* @returns A promise resolving to the latest event or null
|
||
*/
|
||
static findLatest(where: WhereOptions) {
|
||
return this.findOne({
|
||
where,
|
||
order: [["createdAt", "DESC"]],
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 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: Context | APIContext,
|
||
attributes: Omit<Partial<Event>, "ip" | "teamId" | "actorId"> = {},
|
||
defaultAttributes: Pick<Partial<Event>, "ip" | "teamId" | "actorId"> = {},
|
||
options?: CreateOptions<InferAttributes<Event>>
|
||
) {
|
||
const user = ctx.state.auth?.user;
|
||
const authType = ctx.state.auth?.type;
|
||
|
||
return this.create(
|
||
{
|
||
...attributes,
|
||
actorId: user?.id || defaultAttributes.actorId,
|
||
teamId: user?.teamId || defaultAttributes.teamId,
|
||
ip: ctx.request?.ip || defaultAttributes.ip,
|
||
authType,
|
||
},
|
||
{
|
||
transaction: ctx.state.transaction,
|
||
...options,
|
||
}
|
||
);
|
||
}
|
||
}
|
||
|
||
export default Event;
|