Files
outline/server/models/Event.ts
Tom Moor bf45e97641 chore: Enforce type import consistency (#10968)
* Update types

* fix circular dep

* type imports

* lint type imports and --fix
2025-12-19 23:07:02 -05:00

196 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;