Split types and typings
[oweals/peertube.git] / server / models / video / tag.ts
index 2992da56db8f92cc6eed4ab7cb50cfd96d48bc09..adbc4fb7d6c22b2d2015399cafbdef4589575ec4 100644 (file)
@@ -1,73 +1,86 @@
-import * as Sequelize from 'sequelize'
-import * as Promise from 'bluebird'
+import * as Bluebird from 'bluebird'
+import { fn, QueryTypes, Transaction, col } from 'sequelize'
+import { AllowNull, BelongsToMany, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { isVideoTagValid } from '../../helpers/custom-validators/videos'
+import { throwIfNotValid } from '../utils'
+import { VideoModel } from './video'
+import { VideoTagModel } from './video-tag'
+import { VideoPrivacy, VideoState } from '../../../shared/models/videos'
+import { MTag } from '@server/types/models'
 
-import { addMethodsToModel } from '../utils'
-import {
-  TagInstance,
-  TagAttributes,
-
-  TagMethods
-} from './tag-interface'
-
-let Tag: Sequelize.Model<TagInstance, TagAttributes>
-let findOrCreateTags: TagMethods.FindOrCreateTags
-
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  Tag = sequelize.define<TagInstance, TagAttributes>('Tag',
+@Table({
+  tableName: 'tag',
+  timestamps: false,
+  indexes: [
     {
-      name: {
-        type: DataTypes.STRING,
-        allowNull: false
-      }
+      fields: [ 'name' ],
+      unique: true
     },
     {
-      timestamps: false,
-      indexes: [
-        {
-          fields: [ 'name' ],
-          unique: true
-        }
-      ]
+      name: 'tag_lower_name',
+      fields: [ fn('lower', col('name')) ] as any // FIXME: typings
     }
-  )
-
-  const classMethods = [
-    associate,
-
-    findOrCreateTags
   ]
-  addMethodsToModel(Tag, classMethods)
+})
+export class TagModel extends Model<TagModel> {
 
-  return Tag
-}
+  @AllowNull(false)
+  @Is('VideoTag', value => throwIfNotValid(value, isVideoTagValid, 'tag'))
+  @Column
+  name: string
+
+  @CreatedAt
+  createdAt: Date
 
-// ---------------------------------------------------------------------------
+  @UpdatedAt
+  updatedAt: Date
 
-function associate (models) {
-  Tag.belongsToMany(models.Video, {
+  @BelongsToMany(() => VideoModel, {
     foreignKey: 'tagId',
-    through: models.VideoTag,
-    onDelete: 'cascade'
+    through: () => VideoTagModel,
+    onDelete: 'CASCADE'
   })
-}
+  Videos: VideoModel[]
+
+  static findOrCreateTags (tags: string[], transaction: Transaction): Promise<MTag[]> {
+    if (tags === null) return Promise.resolve([])
 
-findOrCreateTags = function (tags: string[], transaction: Sequelize.Transaction) {
-  const tasks: Promise<TagInstance>[] = []
-  tags.forEach(tag => {
-    const query: Sequelize.FindOrInitializeOptions<TagAttributes> = {
-      where: {
-        name: tag
-      },
-      defaults: {
-        name: tag
+    const tasks: Bluebird<MTag>[] = []
+    tags.forEach(tag => {
+      const query = {
+        where: {
+          name: tag
+        },
+        defaults: {
+          name: tag
+        },
+        transaction
       }
-    }
 
-    if (transaction) query.transaction = transaction
+      const promise = TagModel.findOrCreate<MTag>(query)
+        .then(([ tagInstance ]) => tagInstance)
+      tasks.push(promise)
+    })
 
-    const promise = Tag.findOrCreate(query).then(([ tagInstance ]) => tagInstance)
-    tasks.push(promise)
-  })
+    return Promise.all(tasks)
+  }
+
+  // threshold corresponds to how many video the field should have to be returned
+  static getRandomSamples (threshold: number, count: number): Bluebird<string[]> {
+    const query = 'SELECT tag.name FROM tag ' +
+      'INNER JOIN "videoTag" ON "videoTag"."tagId" = tag.id ' +
+      'INNER JOIN video ON video.id = "videoTag"."videoId" ' +
+      'WHERE video.privacy = $videoPrivacy AND video.state = $videoState ' +
+      'GROUP BY tag.name HAVING COUNT(tag.name) >= $threshold ' +
+      'ORDER BY random() ' +
+      'LIMIT $count'
+
+    const options = {
+      bind: { threshold, count, videoPrivacy: VideoPrivacy.PUBLIC, videoState: VideoState.PUBLISHED },
+      type: QueryTypes.SELECT as QueryTypes.SELECT
+    }
 
-  return Promise.all(tasks)
+    return TagModel.sequelize.query<{ name: string }>(query, options)
+                    .then(data => data.map(d => d.name))
+  }
 }