X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fmodels%2Fvideo%2Fvideo-playlist.ts;h=245475f94f8211f8105f70afb0efff773c8e3132;hb=15a7eafb892441957ba7dd6fcbf556086fe5b2b3;hp=b48dd29451dc762e5b6d8aa1676a3e6d61d02faa;hpb=7d9ba5c08999c6482f0bc5e0c09c6f55b7724090;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts index b48dd2945..245475f94 100644 --- a/server/models/video/video-playlist.ts +++ b/server/models/video/video-playlist.ts @@ -1,5 +1,5 @@ import { join } from 'path' -import { FindOptions, literal, Op, ScopeOptions, Transaction, WhereOptions } from 'sequelize' +import { FindOptions, literal, Op, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize' import { AllowNull, BelongsTo, @@ -17,8 +17,10 @@ import { Table, UpdatedAt } from 'sequelize-typescript' -import { v4 as uuidv4 } from 'uuid' +import { setAsUpdated } from '@server/helpers/database-utils' +import { buildUUID, uuidToShort } from '@server/helpers/uuid' import { MAccountId, MChannelId } from '@server/types/models' +import { AttributesOnly, buildPlaylistEmbedPath, buildPlaylistLink, buildPlaylistWatchPath } from '@shared/core-utils' import { ActivityIconObject } from '../../../shared/models/activitypub/objects' import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object' import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' @@ -51,7 +53,15 @@ import { } from '../../types/models/video/video-playlist' import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions } from '../account/account' import { ActorModel } from '../actor/actor' -import { buildServerIdsFollowedBy, buildWhereIdOrUUID, getPlaylistSort, isOutdated, throwIfNotValid } from '../utils' +import { + buildServerIdsFollowedBy, + buildTrigramSearchIndex, + buildWhereIdOrUUID, + createSimilarityAttribute, + getPlaylistSort, + isOutdated, + throwIfNotValid +} from '../utils' import { ThumbnailModel } from './thumbnail' import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel' import { VideoPlaylistElementModel } from './video-playlist-element' @@ -72,6 +82,11 @@ type AvailableForListOptions = { videoChannelId?: number listMyPlaylists?: boolean search?: string + withVideos?: boolean +} + +function getVideoLengthSelect () { + return 'SELECT COUNT("id") FROM "videoPlaylistElement" WHERE "videoPlaylistId" = "VideoPlaylistModel"."id"' } @Scopes(() => ({ @@ -87,7 +102,7 @@ type AvailableForListOptions = { attributes: { include: [ [ - literal('(SELECT COUNT("id") FROM "videoPlaylistElement" WHERE "videoPlaylistId" = "VideoPlaylistModel"."id")'), + literal(`(${getVideoLengthSelect()})`), 'videosLength' ] ] @@ -176,11 +191,28 @@ type AvailableForListOptions = { }) } + if (options.withVideos === true) { + whereAnd.push( + literal(`(${getVideoLengthSelect()}) != 0`) + ) + } + + const attributesInclude = [] + if (options.search) { + const escapedSearch = VideoPlaylistModel.sequelize.escape(options.search) + const escapedLikeSearch = VideoPlaylistModel.sequelize.escape('%' + options.search + '%') + attributesInclude.push(createSimilarityAttribute('VideoPlaylistModel.name', options.search)) + whereAnd.push({ - name: { - [Op.iLike]: '%' + options.search + '%' - } + [Op.or]: [ + Sequelize.literal( + 'lower(immutable_unaccent("VideoPlaylistModel"."name")) % lower(immutable_unaccent(' + escapedSearch + '))' + ), + Sequelize.literal( + 'lower(immutable_unaccent("VideoPlaylistModel"."name")) LIKE lower(immutable_unaccent(' + escapedLikeSearch + '))' + ) + ] }) } @@ -189,6 +221,9 @@ type AvailableForListOptions = { } return { + attributes: { + include: attributesInclude + }, where, include: [ { @@ -209,6 +244,8 @@ type AvailableForListOptions = { @Table({ tableName: 'videoPlaylist', indexes: [ + buildTrigramSearchIndex('video_playlist_name_trigram', 'name'), + { fields: [ 'ownerAccountId' ] }, @@ -221,7 +258,7 @@ type AvailableForListOptions = { } ] }) -export class VideoPlaylistModel extends Model { +export class VideoPlaylistModel extends Model>> { @CreatedAt createdAt: Date @@ -312,6 +349,7 @@ export class VideoPlaylistModel extends Model { videoChannelId?: number listMyPlaylists?: boolean search?: string + withVideos?: boolean // false by default }) { const query = { offset: options.start, @@ -329,7 +367,8 @@ export class VideoPlaylistModel extends Model { accountId: options.accountId, videoChannelId: options.videoChannelId, listMyPlaylists: options.listMyPlaylists, - search: options.search + search: options.search, + withVideos: options.withVideos || false } as AvailableForListOptions ] }, @@ -345,6 +384,21 @@ export class VideoPlaylistModel extends Model { }) } + static searchForApi (options: { + followerActorId: number + start: number + count: number + sort: string + search?: string + }) { + return VideoPlaylistModel.listForApi({ + ...options, + type: VideoPlaylistType.REGULAR, + listMyPlaylists: false, + withVideos: true + }) + } + static listPublicUrlsOfForAP (options: { account?: MAccountId, channel?: MChannelId }, start: number, count: number) { const where = { privacy: VideoPlaylistPrivacy.PUBLIC @@ -443,6 +497,18 @@ export class VideoPlaylistModel extends Model { return VideoPlaylistModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_THUMBNAIL ]).findOne(query) } + static loadByUrlWithAccountAndChannelSummary (url: string): Promise { + const query = { + where: { + url + } + } + + return VideoPlaylistModel + .scope([ ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY, ScopeNames.WITH_VIDEOS_LENGTH, ScopeNames.WITH_THUMBNAIL ]) + .findOne(query) + } + static getPrivacyLabel (privacy: VideoPlaylistPrivacy) { return VIDEO_PLAYLIST_PRIVACIES[privacy] || 'Unknown' } @@ -479,7 +545,7 @@ export class VideoPlaylistModel extends Model { generateThumbnailName () { const extension = '.jpg' - return 'playlist-' + uuidv4() + extension + return 'playlist-' + buildUUID() + extension } getThumbnailUrl () { @@ -494,12 +560,12 @@ export class VideoPlaylistModel extends Model { return join(STATIC_PATHS.THUMBNAILS, this.Thumbnail.filename) } - getWatchUrl () { - return WEBSERVER.URL + '/videos/watch/playlist/' + this.uuid + getWatchStaticPath () { + return buildPlaylistWatchPath({ shortUUID: uuidToShort(this.uuid) }) } getEmbedStaticPath () { - return '/video-playlists/embed/' + this.uuid + return buildPlaylistEmbedPath(this) } static async getStats () { @@ -530,9 +596,11 @@ export class VideoPlaylistModel extends Model { } setAsRefreshed () { - this.changed('updatedAt', true) + return setAsUpdated('videoPlaylist', this.id) + } - return this.save() + setVideosLength (videosLength: number) { + this.set('videosLength' as any, videosLength, { raw: true }) } isOwned () { @@ -549,8 +617,12 @@ export class VideoPlaylistModel extends Model { return { id: this.id, uuid: this.uuid, + shortUUID: uuidToShort(this.uuid), + isLocal: this.isOwned(), + url: this.url, + displayName: this.name, description: this.description, privacy: {