return new RestDataSource(this.authHttp, VideoAbuseService.BASE_VIDEO_ABUSE_URL + 'abuse')
}
- reportVideo (id: string, reason: string) {
+ reportVideo (id: number, reason: string) {
const body = {
reason
}
description: string
duration: number
durationLabel: string
- id: string
+ id: number
+ uuid: string
isLocal: boolean
magnetUri: string
name: string
language: number
description: string,
duration: number
- id: string,
+ id: number,
+ uuid: string,
isLocal: boolean,
magnetUri: string,
name: string,
this.duration = hash.duration
this.durationLabel = Video.createDurationString(hash.duration)
this.id = hash.id
+ this.uuid = hash.uuid
this.isLocal = hash.isLocal
this.magnetUri = hash.magnetUri
this.name = hash.name
return this.loadVideoAttributeEnum('languages', this.videoLanguages)
}
- getVideo (id: string): Observable<Video> {
- return this.http.get(VideoService.BASE_VIDEO_URL + id)
+ getVideo (uuid: string): Observable<Video> {
+ return this.http.get(VideoService.BASE_VIDEO_URL + uuid)
.map(this.restExtractor.extractDataGet)
.map(videoHash => new Video(videoHash))
.catch((res) => this.restExtractor.handleError(res))
.catch((res) => this.restExtractor.handleError(res))
}
- removeVideo (id: string) {
+ removeVideo (id: number) {
return this.authHttp.delete(VideoService.BASE_VIDEO_URL + id)
.map(this.restExtractor.extractDataBool)
.catch((res) => this.restExtractor.handleError(res))
.catch((res) => this.restExtractor.handleError(res))
}
- reportVideo (id: string, reason: string) {
+ reportVideo (id: number, reason: string) {
const url = VideoService.BASE_VIDEO_URL + id + '/abuse'
const body: VideoAbuseCreate = {
reason
.catch((res) => this.restExtractor.handleError(res))
}
- setVideoLike (id: string) {
+ setVideoLike (id: number) {
return this.setVideoRate(id, 'like')
}
- setVideoDislike (id: string) {
+ setVideoDislike (id: number) {
return this.setVideoRate(id, 'dislike')
}
- getUserVideoRating (id: string): Observable<UserVideoRate> {
+ getUserVideoRating (id: number): Observable<UserVideoRate> {
const url = UserService.BASE_USERS_URL + '/me/videos/' + id + '/rating'
return this.authHttp.get(url)
.catch((res) => this.restExtractor.handleError(res))
}
- blacklistVideo (id: string) {
+ blacklistVideo (id: number) {
return this.authHttp.post(VideoService.BASE_VIDEO_URL + id + '/blacklist', {})
.map(this.restExtractor.extractDataBool)
.catch((res) => this.restExtractor.handleError(res))
}
- private setVideoRate (id: string, rateType: VideoRateType) {
+ private setVideoRate (id: number, rateType: VideoRateType) {
const url = VideoService.BASE_VIDEO_URL + id + '/rate'
const body: UserVideoRateUpdate = {
rating: rateType
this.videoLicences = this.videoService.videoLicences
this.videoLanguages = this.videoService.videoLanguages
- const id = this.route.snapshot.params['id']
- this.videoService.getVideo(id)
+ const uuid: string = this.route.snapshot.params['uuid']
+ this.videoService.getVideo(uuid)
.subscribe(
video => {
this.video = video
.subscribe(
() => {
this.notificationsService.success('Success', 'Video updated.')
- this.router.navigate([ '/videos/watch', this.video.id ])
+ this.router.navigate([ '/videos/watch', this.video.uuid ])
},
err => {
<div class="video-miniature">
<a
- [routerLink]="['/videos/watch', video.id]" [attr.title]="video.description"
+ [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.description"
class="video-miniature-thumbnail"
>
<img *ngIf="isVideoNSFWForThisUser() === false" [attr.src]="video.thumbnailUrl" alt="video thumbnail" />
<div class="video-miniature-informations">
<span class="video-miniature-name">
- <a [routerLink]="['/videos/watch', video.id]" [attr.title]="getVideoName()" class="video-miniature-name">{{ getVideoName() }}</a>
+ <a [routerLink]="['/videos/watch', video.uuid]" [attr.title]="getVideoName()" class="video-miniature-name">{{ getVideoName() }}</a>
</span>
<div class="video-miniature-tags">
getVideoIframeCode () {
return '<iframe width="560" height="315" ' +
- 'src="' + window.location.origin + '/videos/embed/' + this.video.id + '" ' +
+ 'src="' + window.location.origin + '/videos/embed/' + this.video.uuid + '" ' +
'frameborder="0" allowfullscreen>' +
'</iframe>'
}
<ul *dropdownMenu class="dropdown-menu" id="more-menu" role="menu" aria-labelledby="single-button">
<li *ngIf="canUserUpdateVideo()" role="menuitem">
- <a class="dropdown-item" title="Update this video" href="#" [routerLink]="[ '/videos/edit', video.id ]">
+ <a class="dropdown-item" title="Update this video" href="#" [routerLink]="[ '/videos/edit', video.uuid ]">
<span class="glyphicon glyphicon-pencil"></span> Update
</a>
</li>
ngOnInit () {
this.paramsSub = this.route.params.subscribe(routeParams => {
- let id = routeParams['id']
- this.videoService.getVideo(id).subscribe(
+ let uuid = routeParams['uuid']
+ this.videoService.getVideo(uuid).subscribe(
video => this.onVideoFetched(video),
error => {
}
},
{
- path: 'edit/:id',
+ path: 'edit/:uuid',
component: VideoUpdateComponent,
data: {
meta: {
}
},
{
- path: ':id',
- redirectTo: 'watch/:id'
+ path: ':uuid',
+ redirectTo: 'watch/:uuid'
},
{
- path: 'watch/:id',
+ path: 'watch/:uuid',
component: VideoWatchComponent
}
]
function processVideosEvents (eventData: RemoteVideoEventData, fromPod: PodInstance) {
return db.sequelize.transaction(t => {
- return fetchOwnedVideo(eventData.remoteId)
+ return fetchVideoByUUID(eventData.uuid)
.then(videoInstance => {
const options = { transaction: t }
return quickAndDirtyUpdatesVideoToFriends(qadusParams, t)
})
})
- .then(() => logger.info('Remote video event processed for video %s.', eventData.remoteId))
+ .then(() => logger.info('Remote video event processed for video %s.', eventData.uuid))
.catch(err => {
logger.debug('Cannot process a video event.', err)
throw err
let videoName
return db.sequelize.transaction(t => {
- return fetchRemoteVideo(fromPod.host, videoData.remoteId)
+ return fetchVideoByHostAndUUID(fromPod.host, videoData.uuid)
.then(videoInstance => {
const options = { transaction: t }
}
function addRemoteVideo (videoToCreateData: RemoteVideoCreateData, fromPod: PodInstance) {
- logger.debug('Adding remote video "%s".', videoToCreateData.remoteId)
+ logger.debug('Adding remote video "%s".', videoToCreateData.uuid)
return db.sequelize.transaction(t => {
- return db.Video.loadByHostAndRemoteId(fromPod.host, videoToCreateData.remoteId)
+ return db.Video.loadByUUID(videoToCreateData.uuid)
.then(video => {
- if (video) throw new Error('RemoteId and host pair is not unique.')
+ if (video) throw new Error('UUID already exists.')
return undefined
})
.then(({ author, tagInstances }) => {
const videoData = {
name: videoToCreateData.name,
- remoteId: videoToCreateData.remoteId,
+ uuid: videoToCreateData.uuid,
extname: videoToCreateData.extname,
infoHash: videoToCreateData.infoHash,
category: videoToCreateData.category,
updatedAt: videoToCreateData.updatedAt,
views: videoToCreateData.views,
likes: videoToCreateData.likes,
- dislikes: videoToCreateData.dislikes
+ dislikes: videoToCreateData.dislikes,
+ remote: true
}
const video = db.Video.build(videoData)
}
function updateRemoteVideo (videoAttributesToUpdate: RemoteVideoUpdateData, fromPod: PodInstance) {
- logger.debug('Updating remote video "%s".', videoAttributesToUpdate.remoteId)
+ logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
return db.sequelize.transaction(t => {
- return fetchRemoteVideo(fromPod.host, videoAttributesToUpdate.remoteId)
+ return fetchVideoByHostAndUUID(fromPod.host, videoAttributesToUpdate.uuid)
.then(videoInstance => {
const tags = videoAttributesToUpdate.tags
function removeRemoteVideo (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
// We need the instance because we have to remove some other stuffs (thumbnail etc)
- return fetchRemoteVideo(fromPod.host, videoToRemoveData.remoteId)
+ return fetchVideoByHostAndUUID(fromPod.host, videoToRemoveData.uuid)
.then(video => {
- logger.debug('Removing remote video %s.', video.remoteId)
+ logger.debug('Removing remote video %s.', video.uuid)
return video.destroy()
})
.catch(err => {
- logger.debug('Could not fetch remote video.', { host: fromPod.host, remoteId: videoToRemoveData.remoteId, error: err.stack })
+ logger.debug('Could not fetch remote video.', { host: fromPod.host, uuid: videoToRemoveData.uuid, error: err.stack })
})
}
function reportAbuseRemoteVideo (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) {
- return fetchOwnedVideo(reportData.videoRemoteId)
+ return fetchVideoByUUID(reportData.videoUUID)
.then(video => {
logger.debug('Reporting remote abuse for video %s.', video.id)
.catch(err => logger.error('Cannot create remote abuse video.', err))
}
-function fetchOwnedVideo (id: string) {
- return db.Video.load(id)
+function fetchVideoByUUID (id: string) {
+ return db.Video.loadByUUID(id)
.then(video => {
if (!video) throw new Error('Video not found')
})
}
-function fetchRemoteVideo (podHost: string, remoteId: string) {
- return db.Video.loadByHostAndRemoteId(podHost, remoteId)
+function fetchVideoByHostAndUUID (podHost: string, uuid: string) {
+ return db.Video.loadByHostAndUUID(podHost, uuid)
.then(video => {
if (!video) throw new Error('Video not found')
return video
})
.catch(err => {
- logger.error('Cannot load video from host and remote id.', { error: err.stack, podHost, remoteId })
+ logger.error('Cannot load video from host and uuid.', { error: err.stack, podHost, uuid })
throw err
})
}
}
function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) {
- const videoId = '' + req.params.videoId
+ const videoId = +req.params.videoId
const userId = +res.locals.oauth.token.User.id
db.UserVideoRate.load(userId, videoId, null)
}
function reportVideoAbuse (req: express.Request, res: express.Response) {
- const videoInstance = res.locals.video
+ const videoInstance = res.locals.video as VideoInstance
const reporterUsername = res.locals.oauth.token.User.username
const body: VideoAbuseCreate = req.body
const reportData = {
reporterUsername,
reportReason: abuse.reason,
- videoRemoteId: videoInstance.remoteId
+ videoUUID: videoInstance.uuid
}
return friends.reportAbuseVideoToFriend(reportData, videoInstance, t).then(() => videoInstance)
.then(({ author, tagInstances }) => {
const videoData = {
name: videoInfos.name,
- remoteId: null,
+ remote: false,
extname: path.extname(videoFile.filename),
category: videoInfos.category,
licence: videoInfos.licence,
// There was a previous rate, update it
if (previousRate) {
- // We will remove the previous rate, so we will need to remove it from the video attribute
+ // We will remove the previous rate, so we will need to update the video count attribute
if (previousRate.type === VIDEO_RATE_TYPES.LIKE) likesToIncrement--
else if (previousRate.type === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement--
}
let tagsString = ''
- Object.keys(metaTags).forEach(function (tagName) {
+ Object.keys(metaTags).forEach(tagName => {
const tagValue = metaTags[tagName]
tagsString += '<meta property="' + tagName + '" content="' + tagValue + '" />'
function generateWatchHtmlPage (req: express.Request, res: express.Response, next: express.NextFunction) {
const videoId = '' + req.params.id
+ let videoPromise: Promise<VideoInstance>
// Let Angular application handle errors
- if (!validator.isUUID(videoId, 4)) return res.sendFile(indexPath)
+ if (validator.isUUID(videoId, 4)) {
+ videoPromise = db.Video.loadByUUIDAndPopulateAuthorAndPodAndTags(videoId)
+ } else if (validator.isInt(videoId)) {
+ videoPromise = db.Video.loadAndPopulateAuthorAndPodAndTags(+videoId)
+ } else {
+ return res.sendFile(indexPath)
+ }
Promise.all([
readFileBufferPromise(indexPath),
- db.Video.loadAndPopulateAuthorAndPodAndTags(videoId)
+ videoPromise
])
.then(([ file, video ]) => {
file = file as Buffer
import {
isVideoAuthorValid,
isVideoThumbnailDataValid,
- isVideoRemoteIdValid,
+ isVideoUUIDValid,
isVideoAbuseReasonValid,
isVideoAbuseReporterUsernameValid,
isVideoViewsValid,
) ||
(
isRequestTypeRemoveValid(request.type) &&
- isVideoRemoteIdValid(video.remoteId)
+ isVideoUUIDValid(video.uuid)
) ||
(
isRequestTypeReportAbuseValid(request.type) &&
- isVideoRemoteIdValid(request.data.videoRemoteId) &&
+ isVideoUUIDValid(request.data.videoUUID) &&
isVideoAbuseReasonValid(request.data.reportReason) &&
isVideoAbuseReporterUsernameValid(request.data.reporterUsername)
)
if (!video) return false
return (
- isVideoRemoteIdValid(video.remoteId) &&
+ isVideoUUIDValid(video.uuid) &&
(has(video, 'views') === false || isVideoViewsValid(video.views)) &&
(has(video, 'likes') === false || isVideoLikesValid(video.likes)) &&
(has(video, 'dislikes') === false || isVideoDislikesValid(video.dislikes))
if (!eventData) return false
return (
- isVideoRemoteIdValid(eventData.remoteId) &&
+ isVideoUUIDValid(eventData.uuid) &&
values(REQUEST_VIDEO_EVENT_TYPES).indexOf(eventData.eventType) !== -1 &&
isVideoEventCountValid(eventData.count)
)
isVideoInfoHashValid(video.infoHash) &&
isVideoNameValid(video.name) &&
isVideoTagsValid(video.tags) &&
- isVideoRemoteIdValid(video.remoteId) &&
+ isVideoUUIDValid(video.uuid) &&
isVideoExtnameValid(video.extname) &&
isVideoViewsValid(video.views) &&
isVideoLikesValid(video.likes) &&
const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES
const VIDEO_EVENTS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_EVENTS
+function isVideoIdOrUUIDValid (value: string) {
+ return validator.isInt(value) || isVideoUUIDValid(value)
+}
+
function isVideoAuthorValid (value: string) {
return isUserUsernameValid(value)
}
return exists(value) && validator.isByteLength(value, VIDEOS_CONSTRAINTS_FIELDS.THUMBNAIL_DATA)
}
-function isVideoRemoteIdValid (value: string) {
- return exists(value) && validator.isUUID(value, 4)
+function isVideoUUIDValid (value: string) {
+ return exists(value) && validator.isUUID('' + value, 4)
}
function isVideoAbuseReasonValid (value: string) {
// ---------------------------------------------------------------------------
export {
+ isVideoIdOrUUIDValid,
isVideoAuthorValid,
isVideoDateValid,
isVideoCategoryValid,
isVideoThumbnailValid,
isVideoThumbnailDataValid,
isVideoExtnameValid,
- isVideoRemoteIdValid,
+ isVideoUUIDValid,
isVideoAbuseReasonValid,
isVideoAbuseReporterUsernameValid,
isVideoFile,
declare global {
namespace ExpressValidator {
export interface Validator {
+ isVideoIdOrUUIDValid,
isVideoAuthorValid,
isVideoDateValid,
isVideoCategoryValid,
isVideoThumbnailValid,
isVideoThumbnailDataValid,
isVideoExtnameValid,
- isVideoRemoteIdValid,
+ isVideoUUIDValid,
isVideoAbuseReasonValid,
isVideoAbuseReporterUsernameValid,
isVideoFile,
// ---------------------------------------------------------------------------
-const LAST_MIGRATION_VERSION = 50
+const LAST_MIGRATION_VERSION = 55
// ---------------------------------------------------------------------------
})
}
-function down (options, callback) {
+function down (options) {
throw new Error('Not implemented.')
}
})
}
-function down (options, callback) {
+function down (options) {
throw new Error('Not implemented.')
}
return q.addColumn('Videos', 'views', data)
}
-function down (options, callback) {
+function down (options) {
throw new Error('Not implemented.')
}
return q.addColumn('Videos', 'likes', data)
}
-function down (options, callback) {
+function down (options) {
throw new Error('Not implemented.')
}
return q.addColumn('Videos', 'dislikes', data)
}
-function down (options, callback) {
+function down (options) {
throw new Error('Not implemented.')
}
})
}
-function down (options, callback) {
+function down (options) {
throw new Error('Not implemented.')
}
})
}
-function down (options, callback) {
+function down (options) {
throw new Error('Not implemented.')
}
})
}
-function down (options, callback) {
+function down (options) {
throw new Error('Not implemented.')
}
return q.addColumn('Users', 'displayNSFW', data)
}
-function down (options, callback) {
+function down (options) {
throw new Error('Not implemented.')
}
return q.addColumn('Videos', 'language', data)
}
-function down (options, callback) {
+function down (options) {
throw new Error('Not implemented.')
}
--- /dev/null
+import * as Sequelize from 'sequelize'
+import * as Promise from 'bluebird'
+
+function up (utils: {
+ transaction: Sequelize.Transaction,
+ queryInterface: Sequelize.QueryInterface,
+ sequelize: Sequelize.Sequelize
+}): Promise<void> {
+ const q = utils.queryInterface
+
+ const dataUUID = {
+ type: Sequelize.UUID,
+ defaultValue: Sequelize.UUIDV4,
+ allowNull: true
+ }
+
+ return q.addColumn('Videos', 'uuid', dataUUID)
+ .then(() => {
+ const query = 'UPDATE "Videos" SET "uuid" = "id" WHERE "remoteId" IS NULL'
+ return utils.sequelize.query(query)
+ })
+ .then(() => {
+ const query = 'UPDATE "Videos" SET "uuid" = "remoteId" WHERE "remoteId" IS NOT NULL'
+ return utils.sequelize.query(query)
+ })
+ .then(() => {
+ dataUUID.defaultValue = null
+
+ return q.changeColumn('Videos', 'uuid', dataUUID)
+ })
+ .then(() => {
+ return removeForeignKey(utils.sequelize, 'RequestVideoQadus')
+ })
+ .then(() => {
+ return removeForeignKey(utils.sequelize, 'RequestVideoEvents')
+ })
+ .then(() => {
+ return removeForeignKey(utils.sequelize, 'BlacklistedVideos')
+ })
+ .then(() => {
+ return removeForeignKey(utils.sequelize, 'UserVideoRates')
+ })
+ .then(() => {
+ return removeForeignKey(utils.sequelize, 'VideoAbuses')
+ })
+ .then(() => {
+ return removeForeignKey(utils.sequelize, 'VideoTags')
+ })
+ .then(() => {
+ const query = 'ALTER TABLE "Videos" DROP CONSTRAINT "Videos_pkey"'
+ return utils.sequelize.query(query)
+ })
+ .then(() => {
+ const query = 'ALTER TABLE "Videos" ADD COLUMN "id2" SERIAL PRIMARY KEY'
+ return utils.sequelize.query(query)
+ })
+ .then(() => {
+ return q.renameColumn('Videos', 'id', 'oldId')
+ })
+ .then(() => {
+ return q.renameColumn('Videos', 'id2', 'id')
+ })
+ .then(() => {
+ return changeForeignKey(q, utils.sequelize, 'RequestVideoQadus', false)
+ })
+ .then(() => {
+ return changeForeignKey(q, utils.sequelize, 'RequestVideoEvents', false)
+ })
+ .then(() => {
+ return changeForeignKey(q, utils.sequelize, 'BlacklistedVideos', false)
+ })
+ .then(() => {
+ return changeForeignKey(q, utils.sequelize, 'UserVideoRates', false)
+ })
+ .then(() => {
+ return changeForeignKey(q, utils.sequelize, 'VideoAbuses', false)
+ })
+ .then(() => {
+ return changeForeignKey(q, utils.sequelize, 'VideoTags', true)
+ })
+ .then(() => {
+ return q.removeColumn('Videos', 'oldId')
+ })
+ .then(() => {
+ const dataRemote = {
+ type: Sequelize.BOOLEAN,
+ defaultValue: false,
+ allowNull: false
+ }
+ return q.addColumn('Videos', 'remote', dataRemote)
+ })
+ .then(() => {
+ const query = 'UPDATE "Videos" SET "remote" = false WHERE "remoteId" IS NULL'
+ return utils.sequelize.query(query)
+ })
+ .then(() => {
+ const query = 'UPDATE "Videos" SET "remote" = true WHERE "remoteId" IS NOT NULL'
+ return utils.sequelize.query(query)
+ })
+ .then(() => {
+ return q.removeColumn('Videos', 'remoteId')
+ })
+}
+
+function down (options) {
+ throw new Error('Not implemented.')
+}
+
+function removeForeignKey (sequelize: Sequelize.Sequelize, tableName: string) {
+ const query = 'ALTER TABLE "' + tableName + '" DROP CONSTRAINT "' + tableName + '_videoId_fkey' + '"'
+ return sequelize.query(query)
+}
+
+function changeForeignKey (q: Sequelize.QueryInterface, sequelize: Sequelize.Sequelize, tableName: string, allowNull: boolean) {
+ const data = {
+ type: Sequelize.INTEGER,
+ allowNull: true
+ }
+
+ return q.addColumn(tableName, 'videoId2', data)
+ .then(() => {
+ const query = 'UPDATE "' + tableName + '" SET "videoId2" = ' +
+ '(SELECT "id" FROM "Videos" WHERE "' + tableName + '"."videoId" = "Videos"."oldId")'
+ return sequelize.query(query)
+ })
+ .then(() => {
+ if (allowNull === false) {
+ data.allowNull = false
+
+ return q.changeColumn(tableName, 'videoId2', data)
+ }
+
+ return Promise.resolve()
+ })
+ .then(() => {
+ return q.removeColumn(tableName, 'videoId')
+ })
+ .then(() => {
+ return q.renameColumn(tableName, 'videoId2', 'videoId')
+ })
+ .then(() => {
+ return q.addIndex(tableName, [ 'videoId' ])
+ })
+ .then(() => {
+ const constraintName = tableName + '_videoId_fkey'
+ const query = 'ALTER TABLE "' + tableName + '" ' +
+ ' ADD CONSTRAINT "' + constraintName + '"' +
+ ' FOREIGN KEY ("videoId") REFERENCES "Videos" ON DELETE CASCADE'
+
+ return sequelize.query(query)
+ })
+}
+
+export {
+ up,
+ down
+}
sequelize: db.sequelize
}
- migrationScript.up(options)
+ return migrationScript.up(options)
.then(() => {
// Update the new migration version
- db.Application.updateMigrationVersion(versionScript, t)
+ return db.Application.updateMigrationVersion(versionScript, t)
})
})
}
Pod as FormatedPod
} from '../../shared'
-type QaduParam = { videoId: string, type: RequestVideoQaduType }
-type EventParam = { videoId: string, type: RequestVideoEventType }
+type QaduParam = { videoId: number, type: RequestVideoQaduType }
+type EventParam = { videoId: number, type: RequestVideoEventType }
const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS]
import { addVideoToFriends } from '../../../lib'
import { VideoInstance } from '../../../models'
-function process (data: { id: string }) {
- return db.Video.loadAndPopulateAuthorAndPodAndTags(data.id).then(video => {
+function process (data: { videoUUID: string }) {
+ return db.Video.loadByUUIDAndPopulateAuthorAndPodAndTags(data.videoUUID).then(video => {
return video.transcodeVideofile().then(() => video)
})
}
export type RequestVideoEventSchedulerOptions = {
type: RequestVideoEventType
- videoId: string
+ videoId: number
count?: number
transaction?: Sequelize.Transaction
}
*/
const eventsPerVideoPerPod: {
[ podId: string ]: {
- [ videoRemoteId: string ]: {
+ [ videoUUID: string ]: {
views?: number
likes?: number
dislikes?: number
requestsToMakeGrouped[toPodId].ids.push(eventToProcess.id)
const eventsPerVideo = eventsPerVideoPerPod[toPodId]
- const remoteId = eventToProcess.video.remoteId
- if (!eventsPerVideo[remoteId]) eventsPerVideo[remoteId] = {}
+ const uuid = eventToProcess.video.uuid
+ if (!eventsPerVideo[uuid]) eventsPerVideo[uuid] = {}
- const events = eventsPerVideo[remoteId]
+ const events = eventsPerVideo[uuid]
if (!events[eventToProcess.type]) events[eventToProcess.type] = 0
events[eventToProcess.type] += eventToProcess.count
Object.keys(eventsPerVideoPerPod).forEach(toPodId => {
const eventsForPod = eventsPerVideoPerPod[toPodId]
- Object.keys(eventsForPod).forEach(remoteId => {
- const eventsForVideo = eventsForPod[remoteId]
+ Object.keys(eventsForPod).forEach(uuid => {
+ const eventsForVideo = eventsForPod[uuid]
Object.keys(eventsForVideo).forEach(eventType => {
requestsToMakeGrouped[toPodId].datas.push({
data: {
- remoteId,
+ uuid,
eventType: eventType as RemoteVideoEventType,
count: +eventsForVideo[eventType]
}
datas: U[]
videos: {
- [ id: string ]: {
- remoteId: string
+ [ uuid: string ]: {
+ uuid: string
likes?: number
dislikes?: number
views?: number
export type RequestVideoQaduSchedulerOptions = {
type: RequestVideoQaduType
- videoId: string
+ videoId: number
transaction?: Sequelize.Transaction
}
// Maybe another attribute was filled for this video
let videoData = requestsToMakeGrouped[hashKey].videos[video.id]
- if (!videoData) videoData = { remoteId: null }
+ if (!videoData) videoData = { uuid: null }
switch (request.type) {
case REQUEST_VIDEO_QADU_TYPES.LIKES:
return
}
- // Do not forget the remoteId so the remote pod can identify the video
- videoData.remoteId = video.id
+ // Do not forget the uuid so the remote pod can identify the video
+ videoData.uuid = video.uuid
requestsToMakeGrouped[hashKey].ids.push(request.id)
// Maybe there are multiple quick and dirty update for the same video
// Now we deduped similar quick and dirty updates, we can build our requests datas
Object.keys(requestsToMakeGrouped).forEach(hashKey => {
- Object.keys(requestsToMakeGrouped[hashKey].videos).forEach(videoId => {
- const videoData = requestsToMakeGrouped[hashKey].videos[videoId]
+ Object.keys(requestsToMakeGrouped[hashKey].videos).forEach(videoUUID => {
+ const videoData = requestsToMakeGrouped[hashKey].videos[videoUUID]
requestsToMakeGrouped[hashKey].datas.push({
data: videoData
import 'express-validator'
import * as express from 'express'
+import * as Promise from 'bluebird'
+import * as validator from 'validator'
import { database as db } from '../../initializers/database'
import { checkErrors } from './utils'
import { logger } from '../../helpers'
+import { VideoInstance } from '../../models'
function usersAddValidator (req: express.Request, res: express.Response, next: express.NextFunction) {
req.checkBody('username', 'Should have a valid username').isUserUsernameValid()
}
function usersVideoRatingValidator (req: express.Request, res: express.Response, next: express.NextFunction) {
- req.checkParams('videoId', 'Should have a valid video id').notEmpty().isUUID(4)
+ req.checkParams('videoId', 'Should have a valid video id').notEmpty().isVideoIdOrUUIDValid()
logger.debug('Checking usersVideoRating parameters', { parameters: req.params })
checkErrors(req, res, function () {
- db.Video.load(req.params.videoId)
+ let videoPromise: Promise<VideoInstance>
+
+ if (validator.isUUID(req.params.videoId)) {
+ videoPromise = db.Video.loadByUUID(req.params.videoId)
+ } else {
+ videoPromise = db.Video.load(req.params.videoId)
+ }
+
+ videoPromise
.then(video => {
if (!video) return res.status(404).send('Video not found')
import 'express-validator'
import * as express from 'express'
+import * as Promise from 'bluebird'
+import * as validator from 'validator'
import { database as db } from '../../initializers/database'
import { checkErrors } from './utils'
import { CONSTRAINTS_FIELDS, SEARCHABLE_COLUMNS } from '../../initializers'
import { logger, isVideoDurationValid } from '../../helpers'
+import { VideoInstance } from '../../models'
function videosAddValidator (req: express.Request, res: express.Response, next: express.NextFunction) {
// FIXME: Don't write an error message, it seems there is a bug with express-validator
}
function videosUpdateValidator (req: express.Request, res: express.Response, next: express.NextFunction) {
- req.checkParams('id', 'Should have a valid id').notEmpty().isUUID(4)
+ req.checkParams('id', 'Should have a valid id').notEmpty().isVideoIdOrUUIDValid()
req.checkBody('name', 'Should have a valid name').optional().isVideoNameValid()
req.checkBody('category', 'Should have a valid category').optional().isVideoCategoryValid()
req.checkBody('licence', 'Should have a valid licence').optional().isVideoLicenceValid()
}
function videosGetValidator (req: express.Request, res: express.Response, next: express.NextFunction) {
- req.checkParams('id', 'Should have a valid id').notEmpty().isUUID(4)
+ req.checkParams('id', 'Should have a valid id').notEmpty().isVideoIdOrUUIDValid()
logger.debug('Checking videosGet parameters', { parameters: req.params })
}
function videosRemoveValidator (req: express.Request, res: express.Response, next: express.NextFunction) {
- req.checkParams('id', 'Should have a valid id').notEmpty().isUUID(4)
+ req.checkParams('id', 'Should have a valid id').notEmpty().isVideoIdOrUUIDValid()
logger.debug('Checking videosRemove parameters', { parameters: req.params })
}
function videoAbuseReportValidator (req: express.Request, res: express.Response, next: express.NextFunction) {
- req.checkParams('id', 'Should have a valid id').notEmpty().isUUID(4)
+ req.checkParams('id', 'Should have a valid id').notEmpty().isVideoIdOrUUIDValid()
req.checkBody('reason', 'Should have a valid reason').isVideoAbuseReasonValid()
logger.debug('Checking videoAbuseReport parameters', { parameters: req.body })
}
function videoRateValidator (req: express.Request, res: express.Response, next: express.NextFunction) {
- req.checkParams('id', 'Should have a valid id').notEmpty().isUUID(4)
+ req.checkParams('id', 'Should have a valid id').notEmpty().isVideoIdOrUUIDValid()
req.checkBody('rating', 'Should have a valid rate type').isVideoRatingTypeValid()
logger.debug('Checking videoRate parameters', { parameters: req.body })
}
function videosBlacklistValidator (req: express.Request, res: express.Response, next: express.NextFunction) {
- req.checkParams('id', 'Should have a valid id').notEmpty().isUUID(4)
+ req.checkParams('id', 'Should have a valid id').notEmpty().isVideoIdOrUUIDValid()
logger.debug('Checking videosBlacklist parameters', { parameters: req.params })
// ---------------------------------------------------------------------------
function checkVideoExists (id: string, res: express.Response, callback: () => void) {
- db.Video.loadAndPopulateAuthorAndPodAndTags(id).then(video => {
+ let promise: Promise<VideoInstance>
+ if (validator.isInt(id)) {
+ promise = db.Video.loadAndPopulateAuthorAndPodAndTags(+id)
+ } else { // UUID
+ promise = db.Video.loadByUUIDAndPopulateAuthorAndPodAndTags(id)
+ }
+
+ promise.then(video => {
if (!video) return res.status(404).send('Video not found')
res.locals.video = video
import { VideoRateType } from '../../../shared/models/videos/video-rate.type'
export namespace UserVideoRateMethods {
- export type Load = (userId: number, videoId: string, transaction: Sequelize.Transaction) => Promise<UserVideoRateInstance>
+ export type Load = (userId: number, videoId: number, transaction: Sequelize.Transaction) => Promise<UserVideoRateInstance>
}
export interface UserVideoRateClass {
})
}
-load = function (userId: number, videoId: string, transaction: Sequelize.Transaction) {
+load = function (userId: number, videoId: number, transaction: Sequelize.Transaction) {
const options: Sequelize.FindOptions = {
where: {
userId,
Tag.belongsToMany(models.Video, {
foreignKey: 'tagId',
through: models.VideoTag,
- onDelete: 'cascade'
+ onDelete: 'CASCADE'
})
}
export interface VideoAbuseAttributes {
reporterUsername: string
reason: string
- videoId: string
+ videoId: number
}
export interface VideoAbuseInstance extends VideoAbuseClass, VideoAbuseAttributes, Sequelize.Instance<VideoAbuseAttributes> {
name: 'reporterPodId',
allowNull: true
},
- onDelete: 'cascade'
+ onDelete: 'CASCADE'
})
VideoAbuse.belongsTo(models.Video, {
name: 'videoId',
allowNull: false
},
- onDelete: 'cascade'
+ onDelete: 'CASCADE'
})
}
export type LoadById = (id: number) => Promise<BlacklistedVideoInstance>
- export type LoadByVideoId = (id: string) => Promise<BlacklistedVideoInstance>
+ export type LoadByVideoId = (id: number) => Promise<BlacklistedVideoInstance>
}
export interface BlacklistedVideoClass {
}
export interface BlacklistedVideoAttributes {
- videoId: string
+ videoId: number
}
export interface BlacklistedVideoInstance
function associate (models) {
BlacklistedVideo.belongsTo(models.Video, {
- foreignKey: 'videoId',
- onDelete: 'cascade'
+ foreignKey: {
+ name: 'videoId',
+ allowNull: false
+ },
+ onDelete: 'CASCADE'
})
}
return BlacklistedVideo.findById(id)
}
-loadByVideoId = function (id: string) {
+loadByVideoId = function (id: number) {
const query = {
where: {
videoId: id
import { ResultList } from '../../../shared/models/result-list.model'
export type FormatedAddRemoteVideo = {
+ uuid: string
name: string
category: number
licence: number
nsfw: boolean
description: string
infoHash: string
- remoteId: string
author: string
duration: number
thumbnailData: string
}
export type FormatedUpdateRemoteVideo = {
+ uuid: string
name: string
category: number
licence: number
nsfw: boolean
description: string
infoHash: string
- remoteId: string
author: string
duration: number
tags: string[]
sort: string
) => Promise< ResultList<VideoInstance> >
- export type Load = (id: string) => Promise<VideoInstance>
- export type LoadByHostAndRemoteId = (fromHost: string, remoteId: string) => Promise<VideoInstance>
- export type LoadAndPopulateAuthor = (id: string) => Promise<VideoInstance>
- export type LoadAndPopulateAuthorAndPodAndTags = (id: string) => Promise<VideoInstance>
+ export type Load = (id: number) => Promise<VideoInstance>
+ export type LoadByUUID = (uuid: string) => Promise<VideoInstance>
+ export type LoadByHostAndUUID = (fromHost: string, uuid: string) => Promise<VideoInstance>
+ export type LoadAndPopulateAuthor = (id: number) => Promise<VideoInstance>
+ export type LoadAndPopulateAuthorAndPodAndTags = (id: number) => Promise<VideoInstance>
+ export type LoadByUUIDAndPopulateAuthorAndPodAndTags = (uuid: string) => Promise<VideoInstance>
}
export interface VideoClass {
getDurationFromFile: VideoMethods.GetDurationFromFile
list: VideoMethods.List
listForApi: VideoMethods.ListForApi
- loadByHostAndRemoteId: VideoMethods.LoadByHostAndRemoteId
+ loadByHostAndUUID: VideoMethods.LoadByHostAndUUID
listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags
listOwnedByAuthor: VideoMethods.ListOwnedByAuthor
load: VideoMethods.Load
+ loadByUUID: VideoMethods.LoadByUUID
loadAndPopulateAuthor: VideoMethods.LoadAndPopulateAuthor
loadAndPopulateAuthorAndPodAndTags: VideoMethods.LoadAndPopulateAuthorAndPodAndTags
+ loadByUUIDAndPopulateAuthorAndPodAndTags: VideoMethods.LoadByUUIDAndPopulateAuthorAndPodAndTags
searchAndPopulateAuthorAndPodAndTags: VideoMethods.SearchAndPopulateAuthorAndPodAndTags
}
export interface VideoAttributes {
+ uuid?: string
name: string
extname: string
- remoteId: string
category: number
licence: number
language: number
views?: number
likes?: number
dislikes?: number
+ remote: boolean
Author?: AuthorInstance
Tags?: TagInstance[]
}
export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> {
- id: string
+ id: number
createdAt: Date
updatedAt: Date
let getDurationFromFile: VideoMethods.GetDurationFromFile
let list: VideoMethods.List
let listForApi: VideoMethods.ListForApi
-let loadByHostAndRemoteId: VideoMethods.LoadByHostAndRemoteId
+let loadByHostAndUUID: VideoMethods.LoadByHostAndUUID
let listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags
let listOwnedByAuthor: VideoMethods.ListOwnedByAuthor
let load: VideoMethods.Load
+let loadByUUID: VideoMethods.LoadByUUID
let loadAndPopulateAuthor: VideoMethods.LoadAndPopulateAuthor
let loadAndPopulateAuthorAndPodAndTags: VideoMethods.LoadAndPopulateAuthorAndPodAndTags
+let loadByUUIDAndPopulateAuthorAndPodAndTags: VideoMethods.LoadByUUIDAndPopulateAuthorAndPodAndTags
let searchAndPopulateAuthorAndPodAndTags: VideoMethods.SearchAndPopulateAuthorAndPodAndTags
export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
Video = sequelize.define<VideoInstance, VideoAttributes>('Video',
{
- id: {
+ uuid: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
- primaryKey: true,
+ allowNull: false,
validate: {
isUUID: 4
}
type: DataTypes.ENUM(values(CONSTRAINTS_FIELDS.VIDEOS.EXTNAME)),
allowNull: false
},
- remoteId: {
- type: DataTypes.UUID,
- allowNull: true,
- validate: {
- isUUID: 4
- }
- },
category: {
type: DataTypes.INTEGER,
allowNull: false,
min: 0,
isInt: true
}
+ },
+ remote: {
+ type: DataTypes.BOOLEAN,
+ allowNull: false,
+ defaultValue: false
}
},
{
{
fields: [ 'authorId' ]
},
- {
- fields: [ 'remoteId' ]
- },
{
fields: [ 'name' ]
},
},
{
fields: [ 'likes' ]
+ },
+ {
+ fields: [ 'uuid' ]
}
],
hooks: {
listOwnedAndPopulateAuthorAndTags,
listOwnedByAuthor,
load,
- loadByHostAndRemoteId,
+ loadByUUID,
+ loadByHostAndUUID,
loadAndPopulateAuthor,
loadAndPopulateAuthorAndPodAndTags,
+ loadByUUIDAndPopulateAuthorAndPodAndTags,
searchAndPopulateAuthorAndPodAndTags,
removeFromBlacklist
]
)
if (CONFIG.TRANSCODING.ENABLED === true) {
+ // Put uuid because we don't have id auto incremented for now
const dataInput = {
- id: video.id
+ videoUUID: video.uuid
}
tasks.push(
if (video.isOwned()) {
const removeVideoToFriendsParams = {
- remoteId: video.id
+ uuid: video.uuid
}
tasks.push(
}
getVideoFilename = function (this: VideoInstance) {
- if (this.isOwned()) return this.id + this.extname
-
- return this.remoteId + this.extname
+ return this.uuid + this.extname
}
getThumbnailName = function (this: VideoInstance) {
// We always have a copy of the thumbnail
- return this.id + '.jpg'
+ const extension = '.jpg'
+ return this.uuid + extension
}
getPreviewName = function (this: VideoInstance) {
const extension = '.jpg'
-
- if (this.isOwned()) return this.id + extension
-
- return this.remoteId + extension
+ return this.uuid + extension
}
getTorrentName = function (this: VideoInstance) {
const extension = '.torrent'
-
- if (this.isOwned()) return this.id + extension
-
- return this.remoteId + extension
+ return this.uuid + extension
}
isOwned = function (this: VideoInstance) {
- return this.remoteId === null
+ return this.remote === false
}
toFormatedJSON = function (this: VideoInstance) {
const json = {
id: this.id,
+ uuid: this.uuid,
name: this.name,
category: this.category,
categoryLabel,
return readFileBufferPromise(thumbnailPath).then(thumbnailData => {
const remoteVideo = {
+ uuid: this.uuid,
name: this.name,
category: this.category,
licence: this.licence,
nsfw: this.nsfw,
description: this.description,
infoHash: this.infoHash,
- remoteId: this.id,
author: this.Author.name,
duration: this.duration,
thumbnailData: thumbnailData.toString('binary'),
toUpdateRemoteJSON = function (this: VideoInstance) {
const json = {
+ uuid: this.uuid,
name: this.name,
category: this.category,
licence: this.licence,
nsfw: this.nsfw,
description: this.description,
infoHash: this.infoHash,
- remoteId: this.id,
author: this.Author.name,
duration: this.duration,
tags: map<TagInstance, string>(this.Tags, 'name'),
})
}
-loadByHostAndRemoteId = function (fromHost: string, remoteId: string) {
+loadByHostAndUUID = function (fromHost: string, uuid: string) {
const query = {
where: {
- remoteId: remoteId
+ uuid
},
include: [
{
}
listOwnedAndPopulateAuthorAndTags = function () {
- // If remoteId is null this is *our* video
const query = {
where: {
- remoteId: null
+ remote: false
},
include: [ Video['sequelize'].models.Author, Video['sequelize'].models.Tag ]
}
listOwnedByAuthor = function (author: string) {
const query = {
where: {
- remoteId: null
+ remote: false
},
include: [
{
return Video.findAll(query)
}
-load = function (id: string) {
+load = function (id: number) {
return Video.findById(id)
}
-loadAndPopulateAuthor = function (id: string) {
+loadByUUID = function (uuid: string) {
+ const query = {
+ where: {
+ uuid
+ }
+ }
+ return Video.findOne(query)
+}
+
+loadAndPopulateAuthor = function (id: number) {
const options = {
include: [ Video['sequelize'].models.Author ]
}
return Video.findById(id, options)
}
-loadAndPopulateAuthorAndPodAndTags = function (id: string) {
+loadAndPopulateAuthorAndPodAndTags = function (id: number) {
const options = {
include: [
{
return Video.findById(id, options)
}
+loadByUUIDAndPopulateAuthorAndPodAndTags = function (uuid: string) {
+ const options = {
+ where: {
+ uuid
+ },
+ include: [
+ {
+ model: Video['sequelize'].models.Author,
+ include: [ { model: Video['sequelize'].models.Pod, required: false } ]
+ },
+ Video['sequelize'].models.Tag
+ ]
+ }
+
+ return Video.findOne(options)
+}
+
searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, start: number, count: number, sort: string) {
const podInclude: Sequelize.IncludeOptions = {
model: Video['sequelize'].models.Pod,
describe('When adding a video', function () {
it('Should check when adding a video')
- it('Should not add an existing remoteId and host pair')
+ it('Should not add an existing uuid')
})
describe('When removing a video', function () {
describe('Test multiple pods', function () {
let servers = []
const toRemove = []
+ let videoUUID = ''
before(function (done) {
this.timeout(120000)
expect(videos[0].name).not.to.equal(toRemove[1].name)
expect(videos[1].name).not.to.equal(toRemove[1].name)
+ videoUUID = videos[0].uuid
+
+ callback()
+ })
+ }, done)
+ })
+
+ it('Should get the same video by UUID on each pod', function (done) {
+ let baseVideo = null
+ each(servers, function (server, callback) {
+ videosUtils.getVideo(server.url, videoUUID, function (err, res) {
+ if (err) throw err
+
+ const video = res.body
+
+ if (baseVideo === null) {
+ baseVideo = video
+ return callback()
+ }
+
+ expect(baseVideo.name).to.equal(video.name)
+ expect(baseVideo.uuid).to.equal(video.uuid)
+ expect(baseVideo.category).to.equal(video.category)
+ expect(baseVideo.language).to.equal(video.language)
+ expect(baseVideo.licence).to.equal(video.licence)
+ expect(baseVideo.category).to.equal(video.category)
+ expect(baseVideo.nsfw).to.equal(video.nsfw)
+ expect(baseVideo.author).to.equal(video.author)
+ expect(baseVideo.tags).to.deep.equal(video.tags)
+
callback()
})
}, done)
describe('Test a single pod', function () {
let server = null
let videoId = -1
+ let videoUUID = ''
let videosListBase = null
before(function (done) {
expect(test).to.equal(true)
videoId = video.id
+ videoUUID = video.uuid
webtorrent.add(video.magnetUri, function (torrent) {
expect(torrent.files).to.exist
if (err) throw err
expect(test).to.equal(true)
- // Wait the async views increment
+ // Wait the async views increment
setTimeout(done, 500)
})
})
})
+ it('Should get the video by UUID', function (done) {
+ // Yes, this could be long
+ this.timeout(60000)
+
+ videosUtils.getVideo(server.url, videoUUID, function (err, res) {
+ if (err) throw err
+
+ const video = res.body
+ expect(video.name).to.equal('my super name')
+
+ // Wait the async views increment
+ setTimeout(done, 500)
+ })
+ })
+
it('Should have the views updated', function (done) {
videosUtils.getVideo(server.url, videoId, function (err, res) {
if (err) throw err
const video = res.body
- expect(video.views).to.equal(1)
+ expect(video.views).to.equal(2)
done()
})
export interface RemoteQaduVideoData {
- remoteId: string
+ uuid: string
views?: number
likes?: number
dislikes?: number
import { RemoteVideoRequest } from './remote-video-request.model'
export interface RemoteVideoCreateData {
- remoteId: string
+ uuid: string
author: string
tags: string[]
name: string
export type RemoteVideoEventType = 'views' | 'likes' | 'dislikes'
export interface RemoteVideoEventData {
- remoteId: string
+ uuid: string
eventType: RemoteVideoEventType
count: number
}
import { RemoteVideoRequest } from './remote-video-request.model'
export interface RemoteVideoRemoveData {
- remoteId: string
+ uuid: string
}
export interface RemoteVideoRemoveRequest extends RemoteVideoRequest {
import { RemoteVideoRequest } from './remote-video-request.model'
export interface RemoteVideoReportAbuseData {
- videoRemoteId: string
+ videoUUID: string
reporterUsername: string
reportReason: string
}
export interface RemoteVideoUpdateData {
- remoteId: string
+ uuid: string
tags: string[]
name: string
extname: string
import { UserVideoRateType } from './user-video-rate.type'
export interface UserVideoRate {
- videoId: string
+ videoId: number
rating: UserVideoRateType
}
reporterPodHost: string
reason: string
reporterUsername: string
- videoId: string
+ videoId: number
createdAt: Date
}
export interface BlacklistedVideo {
id: number
- videoId: string
+ videoId: number
createdAt: Date
}
export interface Video {
- id: string
+ id: number
+ uuid: string
author: string
createdAt: Date
categoryLabel: string