Files
outline/server/models/Event.ts
T
Tom Moor fca10221b9 chore: promote no-explicit-any from warn to error (#12244)
* chore: promote no-explicit-any from warn to error and resolve violations

Upgrades the oxlint rule severity and removes all 40 existing
`no-explicit-any` warnings across the codebase. Most call sites gained
proper types (SharedEditor refs, JSONNode/JSONMark for ProseMirror JSON
walking, DocumentsStore, dd-trace `Span` parameter inference, prosemirror
Fragment public API in place of internal `(fragment as any).content`).
A few load-bearing `any` uses were preserved with scoped disable
comments where changing the type would cascade widely (Sequelize JSONB
columns on `Event`, the `withTracing` higher-order function generic,
`Extension.options` consumed by many subclasses, dd-trace's `req`
patching).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 12:14:23 -04:00

198 lines
5.0 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)
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
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)
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
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;