]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Merge branch 'develop' into pr/1285
authorChocobozzz <me@florianbigard.com>
Mon, 11 Feb 2019 13:09:23 +0000 (14:09 +0100)
committerChocobozzz <me@florianbigard.com>
Mon, 11 Feb 2019 13:09:23 +0000 (14:09 +0100)
20 files changed:
client/src/app/shared/forms/form-validators/video-validators.service.ts
client/src/app/shared/video-import/video-import.service.ts
client/src/app/shared/video/video-edit.model.ts
client/src/app/shared/video/video.model.ts
client/src/app/shared/video/video.service.ts
client/src/app/videos/+video-edit/shared/video-edit.component.html
client/src/app/videos/+video-edit/shared/video-edit.component.ts
client/src/app/videos/+video-watch/video-watch.component.html
client/src/app/videos/+video-watch/video-watch.component.scss
server/controllers/api/videos/index.ts
server/helpers/custom-validators/videos.ts
server/initializers/constants.ts
server/initializers/migrations/0340-add-originally-published-at.ts [new file with mode: 0644]
server/middlewares/validators/videos/videos.ts
server/models/video/video-format-utils.ts
server/models/video/video.ts
shared/models/activitypub/objects/video-torrent-object.ts
shared/models/videos/video-create.model.ts
shared/models/videos/video-update.model.ts
shared/models/videos/video.model.ts

index 81ed0666f11f41693c741ee80a3d6e5b3be0fc0a..e3f7a0969dc5ce3ee1809f0450cfdad03c325d69 100644 (file)
@@ -16,6 +16,7 @@ export class VideoValidatorsService {
   readonly VIDEO_TAGS: BuildFormValidator
   readonly VIDEO_SUPPORT: BuildFormValidator
   readonly VIDEO_SCHEDULE_PUBLICATION_AT: BuildFormValidator
+  readonly VIDEO_ORIGINALLY_PUBLISHED_AT: BuildFormValidator
 
   constructor (private i18n: I18n) {
 
@@ -92,5 +93,10 @@ export class VideoValidatorsService {
         'required': this.i18n('A date is required to schedule video update.')
       }
     }
+
+    this.VIDEO_ORIGINALLY_PUBLISHED_AT = {
+      VALIDATORS: [ ],
+      MESSAGES: {}
+    }
   }
 }
index 2163eb905e9e981333941c01665958a68b63b98f..7ae13154dee7839b185e5ea3ce391959fa95c626 100644 (file)
@@ -67,6 +67,7 @@ export class VideoImportService {
     const description = video.description || null
     const support = video.support || null
     const scheduleUpdate = video.scheduleUpdate || null
+    const originallyPublishedAt = video.originallyPublishedAt || null
 
     return {
       name: video.name,
@@ -84,7 +85,8 @@ export class VideoImportService {
       downloadEnabled: video.downloadEnabled,
       thumbnailfile: video.thumbnailfile,
       previewfile: video.previewfile,
-      scheduleUpdate
+      scheduleUpdate,
+      originallyPublishedAt
     }
   }
 
index 18c62a1f924f1aeeffa58d23fd4f2e5b6cf15b85..c5d5bb406a1649dc5ae631748baed2cc1e28eac1 100644 (file)
@@ -26,6 +26,7 @@ export class VideoEdit implements VideoUpdate {
   uuid?: string
   id?: number
   scheduleUpdate?: VideoScheduleUpdate
+  originallyPublishedAt?: Date | string
 
   constructor (
     video?: Video & {
@@ -56,6 +57,7 @@ export class VideoEdit implements VideoUpdate {
       this.previewUrl = video.previewUrl
 
       this.scheduleUpdate = video.scheduledUpdate
+      this.originallyPublishedAt = new Date(video.originallyPublishedAt)
     }
   }
 
@@ -77,6 +79,12 @@ export class VideoEdit implements VideoUpdate {
     } else {
       this.scheduleUpdate = null
     }
+
+    // Convert originallyPublishedAt to string so that function objectToFormData() works correctly
+    if (this.originallyPublishedAt) {
+      const originallyPublishedAt = new Date(values['originallyPublishedAt'])
+      this.originallyPublishedAt = originallyPublishedAt.toISOString()
+    }
   }
 
   toFormPatch () {
@@ -93,7 +101,8 @@ export class VideoEdit implements VideoUpdate {
       downloadEnabled: this.downloadEnabled,
       waitTranscoding: this.waitTranscoding,
       channelId: this.channelId,
-      privacy: this.privacy
+      privacy: this.privacy,
+      originallyPublishedAt: this.originallyPublishedAt
     }
 
     // Special case if we scheduled an update
index 6ea83d13b4726d3ed8403e83840c112e8c8c1efa..460c09258b732e7f268f6f8ec2c2a26e732db1d4 100644 (file)
@@ -17,6 +17,7 @@ export class Video implements VideoServerModel {
   createdAt: Date
   updatedAt: Date
   publishedAt: Date
+  originallyPublishedAt: Date | string
   category: VideoConstant<number>
   licence: VideoConstant<number>
   language: VideoConstant<string>
@@ -116,6 +117,9 @@ export class Video implements VideoServerModel {
     this.privacy.label = peertubeTranslate(this.privacy.label, translations)
 
     this.scheduledUpdate = hash.scheduledUpdate
+    this.originallyPublishedAt = hash.originallyPublishedAt ?
+    new Date(hash.originallyPublishedAt.toString())
+    : null
     if (this.state) this.state.label = peertubeTranslate(this.state.label, translations)
 
     this.blacklisted = hash.blacklisted
index 565aad93b7936d08106e019283a5ebc9953cc973..960846e21cf52ed4b5cc51274c1e6b2adc0a90cc 100644 (file)
@@ -81,6 +81,7 @@ export class VideoService implements VideosProvider {
     const description = video.description || null
     const support = video.support || null
     const scheduleUpdate = video.scheduleUpdate || null
+    const originallyPublishedAt = video.originallyPublishedAt || null
 
     const body: VideoUpdate = {
       name: video.name,
@@ -98,7 +99,8 @@ export class VideoService implements VideosProvider {
       downloadEnabled: video.downloadEnabled,
       thumbnailfile: video.thumbnailfile,
       previewfile: video.previewfile,
-      scheduleUpdate
+      scheduleUpdate,
+      originallyPublishedAt
     }
 
     const data = objectToFormData(body)
index 59ba15b5310b797e9e92d008efb8467b0687c565..2fb5401701f6aeaf9a57dbe01b0e273fd520a55e 100644 (file)
               </div>
             </div>
 
+            <div class="form-group">
+              <label i18n for="originallyPublishedAt">Original publication date</label>
+              <my-help i18n-preHtml preHtml="This is the date when the content was originally published (e.g. the release date for a film)"></my-help>
+              <p-calendar
+                id="originallyPublishedAt" formControlName="originallyPublishedAt" [dateFormat]="calendarDateFormat"
+                [locale]="calendarLocale" [showTime]="true" [hideOnDateTimeSelect]="true" [monthNavigator]="true" [yearNavigator]="true" [yearRange]="myYearRange"
+              >
+              </p-calendar>
+
+              <div *ngIf="formErrors.originallyPublishedAt" class="form-error">
+                {{ formErrors.originallyPublishedAt }}
+              </div>
+            </div>
+
             <my-peertube-checkbox
               inputName="nsfw" formControlName="nsfw"
               i18n-labelText labelText="This video contains mature or explicit content"
index 3ed7a4a1075afef9f6b7c92d9169c2482eab9a67..83645294846dc97df1ee678a965cdc569a710d2e 100644 (file)
@@ -45,6 +45,7 @@ export class VideoEditComponent implements OnInit, OnDestroy {
 
   calendarLocale: any = {}
   minScheduledDate = new Date()
+  myYearRange = '1880:' + (new Date()).getFullYear()
 
   calendarTimezone: string
   calendarDateFormat: string
@@ -101,7 +102,8 @@ export class VideoEditComponent implements OnInit, OnDestroy {
       thumbnailfile: null,
       previewfile: null,
       support: this.videoValidatorsService.VIDEO_SUPPORT,
-      schedulePublicationAt: this.videoValidatorsService.VIDEO_SCHEDULE_PUBLICATION_AT
+      schedulePublicationAt: this.videoValidatorsService.VIDEO_SCHEDULE_PUBLICATION_AT,
+      originallyPublishedAt: this.videoValidatorsService.VIDEO_ORIGINALLY_PUBLISHED_AT
     }
 
     this.formValidatorService.updateForm(
index 1875230d84b6526cf4b14ac14c98453be5735ce3..6e18ab6a69724f7ccc62d4393398ab38244c79ee 100644 (file)
@@ -33,7 +33,6 @@
           <div>
             <div class="d-block d-sm-none"> <!-- only shown on small devices, has its conterpart for larger viewports below -->
               <h1 class="video-info-name">{{ video.name }}</h1>
-
               <div i18n class="video-info-date-views">
                 Published {{ video.publishedAt | myFromNow }} - {{ video.views | myNumberFormatter }} views
               </div>
             <span class="video-attribute-value">{{ video.privacy.label }}</span>
           </div>
 
+          <div *ngIf="!!video.originallyPublishedAt" class="video-attribute">
+            <span i18n class="video-attribute-label">Originally published</span>
+            <span class="video-attribute-value">{{ video.originallyPublishedAt | date: 'dd MMMM yyyy' }}</span>
+          </div>
+
           <div class="video-attribute">
             <span i18n class="video-attribute-label">Category</span>
             <span *ngIf="!video.category.id" class="video-attribute-value">{{ video.category.label }}</span>
index b03ed197d3fe932318d1c8addf6c12c051f369ef..cfe3533b665ec38ef00b0e8f99978164452775ff 100644 (file)
@@ -286,7 +286,7 @@ $other-videos-width: 260px;
       margin-bottom: 12px;
 
       .video-attribute-label {
-        min-width: 91px;
+        min-width: 142px;
         padding-right: 5px;
         display: inline-block;
         color: $grey-foreground-color;
index 76a318d13b0951282bde551b61339becd1836e6b..6ac13e6a4401ad996c25ff0b19b09844a67e57a4 100644 (file)
@@ -190,7 +190,8 @@ async function addVideo (req: express.Request, res: express.Response) {
     support: videoInfo.support,
     privacy: videoInfo.privacy,
     duration: videoPhysicalFile['duration'], // duration was added by a previous middleware
-    channelId: res.locals.videoChannel.id
+    channelId: res.locals.videoChannel.id,
+    originallyPublishedAt: videoInfo.originallyPublishedAt
   }
   const video = new VideoModel(videoData)
   video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object
@@ -328,6 +329,11 @@ async function updateVideo (req: express.Request, res: express.Response) {
       if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description)
       if (videoInfoToUpdate.commentsEnabled !== undefined) videoInstance.set('commentsEnabled', videoInfoToUpdate.commentsEnabled)
       if (videoInfoToUpdate.downloadEnabled !== undefined) videoInstance.set('downloadEnabled', videoInfoToUpdate.downloadEnabled)
+
+      if (videoInfoToUpdate.originallyPublishedAt !== undefined && videoInfoToUpdate.originallyPublishedAt !== null) {
+        videoInstance.set('originallyPublishedAt', videoInfoToUpdate.originallyPublishedAt)
+      }
+
       if (videoInfoToUpdate.privacy !== undefined) {
         const newPrivacy = parseInt(videoInfoToUpdate.privacy.toString(), 10)
         videoInstance.set('privacy', newPrivacy)
index 95e256b8fb9e8fdbe72959651dade5e7fdac58bd..dd04aa5f65c4346aac6fa04d44efd2d72bb9d37b 100644 (file)
@@ -13,7 +13,7 @@ import {
   VIDEO_STATES
 } from '../../initializers'
 import { VideoModel } from '../../models/video/video'
-import { exists, isArray, isFileValid } from './misc'
+import { exists, isArray, isDateValid, isFileValid } from './misc'
 import { VideoChannelModel } from '../../models/video/video-channel'
 import { UserModel } from '../../models/account/user'
 import * as magnetUtil from 'magnet-uri'
@@ -115,6 +115,10 @@ function isScheduleVideoUpdatePrivacyValid (value: number) {
     )
 }
 
+function isVideoOriginallyPublishedAtValid (value: string | null) {
+  return value === null || isDateValid(value)
+}
+
 function isVideoFileInfoHashValid (value: string | null | undefined) {
   return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.INFO_HASH)
 }
@@ -220,6 +224,7 @@ export {
   isVideoTagsValid,
   isVideoFPSResolutionValid,
   isScheduleVideoUpdatePrivacyValid,
+  isVideoOriginallyPublishedAtValid,
   isVideoFile,
   isVideoMagnetUriValid,
   isVideoStateValid,
index 639493d737333da20cef47b0c9a9602aefe63b0e..3656a23f966b2fbe245ceffce21d4df2a7faf2fd 100644 (file)
@@ -16,7 +16,7 @@ let config: IConfig = require('config')
 
 // ---------------------------------------------------------------------------
 
-const LAST_MIGRATION_VERSION = 335
+const LAST_MIGRATION_VERSION = 340
 
 // ---------------------------------------------------------------------------
 
diff --git a/server/initializers/migrations/0340-add-originally-published-at.ts b/server/initializers/migrations/0340-add-originally-published-at.ts
new file mode 100644 (file)
index 0000000..ab8d668
--- /dev/null
@@ -0,0 +1,31 @@
+import * as Sequelize from 'sequelize'
+
+async function up (utils: {
+  transaction: Sequelize.Transaction,
+  queryInterface: Sequelize.QueryInterface,
+  sequelize: Sequelize.Sequelize
+}): Promise<void> {
+
+  {
+    const data = {
+      type: Sequelize.DATE,
+      allowNull: true,
+      defaultValue: Sequelize.NOW
+    }
+    await utils.queryInterface.addColumn('video', 'originallyPublishedAt', data)
+  }
+
+  {
+    const query = 'UPDATE video SET "originallyPublishedAt" = video."publishedAt"'
+    await utils.sequelize.query(query)
+  }
+}
+
+function down (options) {
+  throw new Error('Not implemented.')
+}
+
+export {
+  up,
+  down
+}
index d9626929c649c96e88d30e2f1c4d8e428b6c143d..159727e2856d11dea05aa172e414ac27ae30f56e 100644 (file)
@@ -14,6 +14,7 @@ import {
 } from '../../../helpers/custom-validators/misc'
 import {
   checkUserCanManageVideo,
+  isVideoOriginallyPublishedAtValid,
   isScheduleVideoUpdatePrivacyValid,
   isVideoCategoryValid,
   isVideoChannelOfAccountExist,
@@ -344,7 +345,10 @@ function getCommonVideoAttributes () {
       .optional()
       .toBoolean()
       .custom(isBooleanValid).withMessage('Should have downloading enabled boolean'),
-
+    body('originallyPublishedAt')
+        .optional()
+        .customSanitizer(toValueOrNull)
+        .custom(isVideoOriginallyPublishedAtValid).withMessage('Should have a valid original publication date'),
     body('scheduleUpdate')
       .optional()
       .customSanitizer(toValueOrNull),
index 76d0445d4bb5ecdc697f0b31ee7f17bb80e316b1..c63285e25ae6aafcebb331365d6159fc21005e8c 100644 (file)
@@ -67,6 +67,7 @@ function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormatting
     createdAt: video.createdAt,
     updatedAt: video.updatedAt,
     publishedAt: video.publishedAt,
+    originallyPublishedAt: video.originallyPublishedAt,
     account: {
       id: formattedAccount.id,
       uuid: formattedAccount.uuid,
@@ -323,6 +324,9 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject {
     commentsEnabled: video.commentsEnabled,
     downloadEnabled: video.downloadEnabled,
     published: video.publishedAt.toISOString(),
+    originallyPublishedAt: video.originallyPublishedAt ?
+      video.originallyPublishedAt.toISOString() :
+      null,
     updated: video.updatedAt.toISOString(),
     mediaType: 'text/markdown',
     content: video.getTruncatedDescription(),
index 0feeed4f8d10270d37408f9d3d1409b7395f6121..73626b6a08a9212ef515764b4169e17bef4a2203 100644 (file)
@@ -103,6 +103,7 @@ const indexes: Sequelize.DefineIndexesOptions[] = [
 
   { fields: [ 'createdAt' ] },
   { fields: [ 'publishedAt' ] },
+  { fields: [ 'originallyPublishedAt' ] },
   { fields: [ 'duration' ] },
   { fields: [ 'views' ] },
   { fields: [ 'channelId' ] },
@@ -740,6 +741,9 @@ export class VideoModel extends Model<VideoModel> {
   @Column
   publishedAt: Date
 
+  @Column
+  originallyPublishedAt: Date
+
   @ForeignKey(() => VideoChannelModel)
   @Column
   channelId: number
index 4231fbb682a6856d8a601ac754816dd6af1edf14..239822bc48497f31f9c32722405ea5f7a767d77f 100644 (file)
@@ -25,6 +25,7 @@ export interface VideoTorrentObject {
   waitTranscoding: boolean
   state: VideoState
   published: string
+  originallyPublishedAt: string
   updated: string
   mediaType: 'text/markdown'
   content: string
index f153a1d00bdf99eea83f910488fc30c7a04d2cb3..53631bf790b35985db6edfd36f11eec41bd410ae 100644 (file)
@@ -16,4 +16,5 @@ export interface VideoCreate {
   downloadEnabled?: boolean
   privacy: VideoPrivacy
   scheduleUpdate?: VideoScheduleUpdate
+  originallyPublishedAt: Date | string
 }
index 6f96633ae480a19650dfeb9ee753c14e03baa3ee..4ef9041560edc68b1615f41d005e839e5125edfd 100644 (file)
@@ -18,4 +18,5 @@ export interface VideoUpdate {
   thumbnailfile?: Blob
   previewfile?: Blob
   scheduleUpdate?: VideoScheduleUpdate
+  originallyPublishedAt?: Date | string
 }
index 891831a9e796858d7c65ea193518364ded6aee89..df800461c0b7095e4a58ca8d4a510c4926ffb57c 100644 (file)
@@ -44,6 +44,7 @@ export interface Video {
   createdAt: Date | string
   updatedAt: Date | string
   publishedAt: Date | string
+  originallyPublishedAt: Date | string
   category: VideoConstant<number>
   licence: VideoConstant<number>
   language: VideoConstant<string>