import { FindAndCountOptions, Op, Transaction } from 'sequelize' import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Table, UpdatedAt } from 'sequelize-typescript' import { MLiveVideo } from '@server/types/models/index.js' import { UserModel } from '../user/user.js' import { SequelizeModel } from '../shared/index.js' export enum LiveVideoStatus { SCHEDULED = 'scheduled', CANCELLED = 'cancelled', COMPLETED = 'completed' } @Table({ tableName: 'liveVideo', indexes: [ { fields: [ 'targetCamera' ] }, { fields: [ 'startTime' ] }, { fields: [ 'status' ] } ] }) export class LiveVideoModel extends SequelizeModel { @AllowNull(false) @Column declare title: string @AllowNull(true) @Column declare thumbnailFilename: string | null @AllowNull(true) @Column(DataType.BLOB) declare thumbnailData: Buffer | null @AllowNull(true) @Column declare thumbnailMimeType: string | null @AllowNull(true) @Column(DataType.BIGINT) declare thumbnailSize: number | null @AllowNull(true) @Column declare thumbnailEtag: string | null @AllowNull(false) @Column declare videoOriginalName: string @AllowNull(false) @Column(DataType.TEXT) declare videoStoragePath: string @AllowNull(false) @Column declare videoMimeType: string @AllowNull(false) @Column(DataType.BIGINT) declare videoSize: number @AllowNull(false) @Column declare startTime: Date @AllowNull(false) @Column declare endTime: Date @AllowNull(false) @Column declare targetCamera: string @AllowNull(false) @Default(LiveVideoStatus.SCHEDULED) @Column declare status: LiveVideoStatus @ForeignKey(() => UserModel) @Column declare createdByUserId: number @BelongsTo(() => UserModel, { foreignKey: { allowNull: false }, onDelete: 'restrict' }) declare CreatedBy: Awaited @CreatedAt declare createdAt: Date @UpdatedAt declare updatedAt: Date static listForApi (options: { start?: number count?: number targetCamera?: string status?: string transaction?: Transaction }) { const query: FindAndCountOptions = { offset: options.start, limit: options.count, order: [ [ 'startTime', 'DESC' ] ], transaction: options.transaction } const where: Record = {} if (options.targetCamera) where.targetCamera = options.targetCamera if (options.status) where.status = options.status if (Object.keys(where).length !== 0) query.where = where return LiveVideoModel.findAndCountAll(query) } static loadById (id: number, transaction?: Transaction) { return LiveVideoModel.findByPk(id, { transaction }) } static loadByThumbnailFilename (thumbnailFilename: string, transaction?: Transaction) { return LiveVideoModel.findOne({ where: { thumbnailFilename }, transaction }) } static listConflicts (options: { targetCamera: string startTime: Date endTime: Date excludeId?: number transaction?: Transaction }) { const where: Record = { targetCamera: options.targetCamera, status: { [Op.not]: LiveVideoStatus.CANCELLED } } if (options.excludeId) { where.id = { [Op.ne]: options.excludeId } } return LiveVideoModel.findAll({ where, transaction: options.transaction }) } static listForStarting (options: { startBefore: Date endAfter: Date transaction?: Transaction }) { return LiveVideoModel.findAll({ where: { status: LiveVideoStatus.SCHEDULED, startTime: { [Op.lte]: options.startBefore }, endTime: { [Op.gt]: options.endAfter } }, order: [ [ 'startTime', 'ASC' ] ], transaction: options.transaction }) } toFormattedJSON (this: LiveVideoModel) { return { id: this.id, title: this.title, thumbnailFilename: this.thumbnailFilename, videoOriginalName: this.videoOriginalName, videoStoragePath: this.videoStoragePath, videoMimeType: this.videoMimeType, videoSize: this.videoSize, startTime: this.startTime.toISOString(), endTime: this.endTime.toISOString(), targetCamera: this.targetCamera, status: this.status, createdByUserId: this.createdByUserId, createdAt: this.createdAt.toISOString(), updatedAt: this.updatedAt.toISOString() } } }