-
+
+
{{ titlePage }}
diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts
index 728c864e9..642a85f65 100644
--- a/client/src/app/shared/video/abstract-video-list.ts
+++ b/client/src/app/shared/video/abstract-video-list.ts
@@ -29,6 +29,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy {
syndicationItems = []
loadOnInit = true
+ marginContent = true
pageHeight: number
videoWidth: number
videoHeight: number
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts
index ef8babd55..f82aa7389 100644
--- a/client/src/app/shared/video/video.service.ts
+++ b/client/src/app/shared/video/video.service.ts
@@ -21,6 +21,8 @@ import { VideoDetails } from './video-details.model'
import { VideoEdit } from './video-edit.model'
import { Video } from './video.model'
import { objectToFormData } from '@app/shared/misc/utils'
+import { Account } from '@app/shared/account/account.model'
+import { AccountService } from '@app/shared/account/account.service'
@Injectable()
export class VideoService {
@@ -97,6 +99,22 @@ export class VideoService {
.catch((res) => this.restExtractor.handleError(res))
}
+ getAccountVideos (
+ account: Account,
+ videoPagination: ComponentPagination,
+ sort: VideoSortField
+ ): Observable<{ videos: Video[], totalVideos: number}> {
+ const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
+
+ let params = new HttpParams()
+ params = this.restService.addRestGetParams(params, pagination, sort)
+
+ return this.authHttp
+ .get(AccountService.BASE_ACCOUNT_URL + account.id + '/videos', { params })
+ .map(this.extractVideos)
+ .catch((res) => this.restExtractor.handleError(res))
+ }
+
getVideos (
videoPagination: ComponentPagination,
sort: VideoSortField,
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html
index 91e590094..abda5043e 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.html
+++ b/client/src/app/videos/+video-watch/video-watch.component.html
@@ -22,12 +22,10 @@
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts
index 6f6f02378..4b0c49583 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch.component.ts
@@ -39,8 +39,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
otherVideosDisplayed: Video[] = []
- syndicationItems = {}
-
player: videojs.Player
playerElement: HTMLVideoElement
userRating: UserVideoRateType = null
@@ -110,7 +108,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
const startTime = this.route.snapshot.queryParams.start
this.onVideoFetched(video, startTime)
.catch(err => this.handleError(err))
- this.generateSyndicationList()
},
error => {
@@ -247,10 +244,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
return this.video.tags.join(', ')
}
- generateSyndicationList () {
- this.syndicationItems = this.videoService.getAccountFeedUrls(this.video.account.id)
- }
-
isVideoRemovable () {
return this.video.isRemovableBy(this.authService.getUser())
}
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss
index 7e7a38bbd..cbf9b566a 100644
--- a/client/src/sass/include/_mixins.scss
+++ b/client/src/sass/include/_mixins.scss
@@ -314,3 +314,10 @@
left: 0.25em;
transform: rotate(-135deg);
}
+
+@mixin in-content-small-title {
+ text-transform: uppercase;
+ color: $orange-color;
+ font-weight: $font-bold;
+ font-size: 13px;
+}
\ No newline at end of file
diff --git a/server/controllers/api/accounts.ts b/server/controllers/api/accounts.ts
index 4dc0cc16d..06ab04033 100644
--- a/server/controllers/api/accounts.ts
+++ b/server/controllers/api/accounts.ts
@@ -1,8 +1,11 @@
import * as express from 'express'
import { getFormattedObjects } from '../../helpers/utils'
-import { asyncMiddleware, paginationValidator, setDefaultSort, setDefaultPagination } from '../../middlewares'
-import { accountsGetValidator, accountsSortValidator } from '../../middlewares/validators'
+import { asyncMiddleware, optionalAuthenticate, paginationValidator, setDefaultPagination, setDefaultSort } from '../../middlewares'
+import { accountsGetValidator, accountsSortValidator, videosSortValidator } from '../../middlewares/validators'
import { AccountModel } from '../../models/account/account'
+import { VideoModel } from '../../models/video/video'
+import { VideoSortField } from '../../../client/src/app/shared/video/sort-field.type'
+import { isNSFWHidden } from '../../helpers/express-utils'
const accountsRouter = express.Router()
@@ -19,6 +22,16 @@ accountsRouter.get('/:id',
getAccount
)
+accountsRouter.get('/:id/videos',
+ asyncMiddleware(accountsGetValidator),
+ paginationValidator,
+ videosSortValidator,
+ setDefaultSort,
+ setDefaultPagination,
+ optionalAuthenticate,
+ asyncMiddleware(getAccountVideos)
+)
+
// ---------------------------------------------------------------------------
export {
@@ -28,7 +41,9 @@ export {
// ---------------------------------------------------------------------------
function getAccount (req: express.Request, res: express.Response, next: express.NextFunction) {
- return res.json(res.locals.account.toFormattedJSON())
+ const account: AccountModel = res.locals.account
+
+ return res.json(account.toFormattedJSON())
}
async function listAccounts (req: express.Request, res: express.Response, next: express.NextFunction) {
@@ -36,3 +51,19 @@ async function listAccounts (req: express.Request, res: express.Response, next:
return res.json(getFormattedObjects(resultList.data, resultList.total))
}
+
+async function getAccountVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
+ const account: AccountModel = res.locals.account
+
+ const resultList = await VideoModel.listForApi(
+ req.query.start as number,
+ req.query.count as number,
+ req.query.sort as VideoSortField,
+ isNSFWHidden(res),
+ null,
+ false,
+ account.id
+ )
+
+ return res.json(getFormattedObjects(resultList.data, resultList.total))
+}
diff --git a/server/controllers/api/index.ts b/server/controllers/api/index.ts
index 3b499f3b7..964d5d04c 100644
--- a/server/controllers/api/index.ts
+++ b/server/controllers/api/index.ts
@@ -1,5 +1,4 @@
import * as express from 'express'
-import { badRequest } from '../../helpers/utils'
import { configRouter } from './config'
import { jobsRouter } from './jobs'
import { oauthClientsRouter } from './oauth-clients'
@@ -7,6 +6,7 @@ import { serverRouter } from './server'
import { usersRouter } from './users'
import { accountsRouter } from './accounts'
import { videosRouter } from './videos'
+import { badRequest } from '../../helpers/express-utils'
const apiRouter = express.Router()
diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts
index 6540adb1c..474329b58 100644
--- a/server/controllers/api/users.ts
+++ b/server/controllers/api/users.ts
@@ -7,7 +7,7 @@ import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRat
import { retryTransactionWrapper } from '../../helpers/database-utils'
import { processImage } from '../../helpers/image-utils'
import { logger } from '../../helpers/logger'
-import { createReqFiles, getFormattedObjects } from '../../helpers/utils'
+import { getFormattedObjects } from '../../helpers/utils'
import { AVATARS_SIZE, CONFIG, IMAGE_MIMETYPE_EXT, RATES_LIMIT, sequelizeTypescript } from '../../initializers'
import { updateActorAvatarInstance } from '../../lib/activitypub'
import { sendUpdateActor } from '../../lib/activitypub/send'
@@ -43,6 +43,7 @@ import { UserModel } from '../../models/account/user'
import { OAuthTokenModel } from '../../models/oauth/oauth-token'
import { VideoModel } from '../../models/video/video'
import { VideoSortField } from '../../../client/src/app/shared/video/sort-field.type'
+import { createReqFiles } from '../../helpers/express-utils'
const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR })
const loginRateLimiter = new RateLimit({
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 6e8601fa1..61b6c5826 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -6,7 +6,7 @@ import { retryTransactionWrapper } from '../../../helpers/database-utils'
import { getVideoFileResolution } from '../../../helpers/ffmpeg-utils'
import { processImage } from '../../../helpers/image-utils'
import { logger } from '../../../helpers/logger'
-import { createReqFiles, getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils'
+import { getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils'
import {
CONFIG,
IMAGE_MIMETYPE_EXT,
@@ -19,11 +19,7 @@ import {
VIDEO_MIMETYPE_EXT,
VIDEO_PRIVACIES
} from '../../../initializers'
-import {
- fetchRemoteVideoDescription,
- getVideoActivityPubUrl,
- shareVideoByServerAndChannel
-} from '../../../lib/activitypub'
+import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServerAndChannel } from '../../../lib/activitypub'
import { sendCreateVideo, sendCreateView, sendUpdateVideo } from '../../../lib/activitypub/send'
import { JobQueue } from '../../../lib/job-queue'
import { Redis } from '../../../lib/redis'
@@ -49,9 +45,9 @@ import { blacklistRouter } from './blacklist'
import { videoChannelRouter } from './channel'
import { videoCommentRouter } from './comment'
import { rateVideoRouter } from './rate'
-import { User } from '../../../../shared/models/users'
import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
import { VideoSortField } from '../../../../client/src/app/shared/video/sort-field.type'
+import { isNSFWHidden, createReqFiles } from '../../../helpers/express-utils'
const videosRouter = express.Router()
@@ -444,12 +440,3 @@ async function searchVideos (req: express.Request, res: express.Response, next:
return res.json(getFormattedObjects(resultList.data, resultList.total))
}
-
-function isNSFWHidden (res: express.Response) {
- if (res.locals.oauth) {
- const user: User = res.locals.oauth.token.User
- if (user) return user.nsfwPolicy === 'do_not_list'
- }
-
- return CONFIG.INSTANCE.DEFAULT_NSFW_POLICY === 'do_not_list'
-}
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts
index 4a4dc3820..6a6af3e09 100644
--- a/server/controllers/feeds.ts
+++ b/server/controllers/feeds.ts
@@ -30,29 +30,18 @@ async function generateFeed (req: express.Request, res: express.Response, next:
let feed = initFeed()
const start = 0
- let resultList: ResultList
const account: AccountModel = res.locals.account
const hideNSFW = CONFIG.INSTANCE.DEFAULT_NSFW_POLICY === 'do_not_list'
- if (account) {
- resultList = await VideoModel.listAccountVideosForApi(
- account.id,
- start,
- FEEDS.COUNT,
- req.query.sort as VideoSortField,
- hideNSFW,
- true
- )
- } else {
- resultList = await VideoModel.listForApi(
- start,
- FEEDS.COUNT,
- req.query.sort as VideoSortField,
- hideNSFW,
- req.query.filter,
- true
- )
- }
+ const resultList = await VideoModel.listForApi(
+ start,
+ FEEDS.COUNT,
+ req.query.sort as VideoSortField,
+ hideNSFW,
+ req.query.filter,
+ true,
+ account ? account.id : null
+ )
// Adding video items to the feed, one at a time
resultList.data.forEach(video => {
diff --git a/server/helpers/express-utils.ts b/server/helpers/express-utils.ts
new file mode 100644
index 000000000..d023117a8
--- /dev/null
+++ b/server/helpers/express-utils.ts
@@ -0,0 +1,77 @@
+import * as express from 'express'
+import * as multer from 'multer'
+import { CONFIG, REMOTE_SCHEME } from '../initializers'
+import { logger } from './logger'
+import { User } from '../../shared/models/users'
+import { generateRandomString } from './utils'
+
+function isNSFWHidden (res: express.Response) {
+ if (res.locals.oauth) {
+ const user: User = res.locals.oauth.token.User
+ if (user) return user.nsfwPolicy === 'do_not_list'
+ }
+
+ return CONFIG.INSTANCE.DEFAULT_NSFW_POLICY === 'do_not_list'
+}
+
+function getHostWithPort (host: string) {
+ const splitted = host.split(':')
+
+ // The port was not specified
+ if (splitted.length === 1) {
+ if (REMOTE_SCHEME.HTTP === 'https') return host + ':443'
+
+ return host + ':80'
+ }
+
+ return host
+}
+
+function badRequest (req: express.Request, res: express.Response, next: express.NextFunction) {
+ return res.type('json').status(400).end()
+}
+
+function createReqFiles (
+ fieldNames: string[],
+ mimeTypes: { [ id: string ]: string },
+ destinations: { [ fieldName: string ]: string }
+) {
+ const storage = multer.diskStorage({
+ destination: (req, file, cb) => {
+ cb(null, destinations[ file.fieldname ])
+ },
+
+ filename: async (req, file, cb) => {
+ const extension = mimeTypes[ file.mimetype ]
+ let randomString = ''
+
+ try {
+ randomString = await generateRandomString(16)
+ } catch (err) {
+ logger.error('Cannot generate random string for file name.', { err })
+ randomString = 'fake-random-string'
+ }
+
+ cb(null, randomString + extension)
+ }
+ })
+
+ const fields = []
+ for (const fieldName of fieldNames) {
+ fields.push({
+ name: fieldName,
+ maxCount: 1
+ })
+ }
+
+ return multer({ storage }).fields(fields)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ isNSFWHidden,
+ getHostWithPort,
+ badRequest,
+ createReqFiles
+}
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts
index c58117219..058c3211e 100644
--- a/server/helpers/utils.ts
+++ b/server/helpers/utils.ts
@@ -1,68 +1,13 @@
-import * as express from 'express'
-import * as multer from 'multer'
import { Model } from 'sequelize-typescript'
import { ResultList } from '../../shared'
import { VideoResolution } from '../../shared/models/videos'
-import { CONFIG, REMOTE_SCHEME } from '../initializers'
+import { CONFIG } from '../initializers'
import { UserModel } from '../models/account/user'
import { ActorModel } from '../models/activitypub/actor'
import { ApplicationModel } from '../models/application/application'
import { pseudoRandomBytesPromise } from './core-utils'
import { logger } from './logger'
-function getHostWithPort (host: string) {
- const splitted = host.split(':')
-
- // The port was not specified
- if (splitted.length === 1) {
- if (REMOTE_SCHEME.HTTP === 'https') return host + ':443'
-
- return host + ':80'
- }
-
- return host
-}
-
-function badRequest (req: express.Request, res: express.Response, next: express.NextFunction) {
- return res.type('json').status(400).end()
-}
-
-function createReqFiles (
- fieldNames: string[],
- mimeTypes: { [ id: string ]: string },
- destinations: { [ fieldName: string ]: string }
-) {
- const storage = multer.diskStorage({
- destination: (req, file, cb) => {
- cb(null, destinations[file.fieldname])
- },
-
- filename: async (req, file, cb) => {
- const extension = mimeTypes[file.mimetype]
- let randomString = ''
-
- try {
- randomString = await generateRandomString(16)
- } catch (err) {
- logger.error('Cannot generate random string for file name.', { err })
- randomString = 'fake-random-string'
- }
-
- cb(null, randomString + extension)
- }
- })
-
- const fields = []
- for (const fieldName of fieldNames) {
- fields.push({
- name: fieldName,
- maxCount: 1
- })
- }
-
- return multer({ storage }).fields(fields)
-}
-
async function generateRandomString (size: number) {
const raw = await pseudoRandomBytesPromise(size)
@@ -151,14 +96,11 @@ type SortType = { sortModel: any, sortValue: string }
// ---------------------------------------------------------------------------
export {
- badRequest,
generateRandomString,
getFormattedObjects,
isSignupAllowed,
computeResolutionsToTranscode,
resetSequelizeInstance,
getServerActor,
- SortType,
- getHostWithPort,
- createReqFiles
+ SortType
}
diff --git a/server/middlewares/servers.ts b/server/middlewares/servers.ts
index a9dcad2d4..c52f4685b 100644
--- a/server/middlewares/servers.ts
+++ b/server/middlewares/servers.ts
@@ -1,6 +1,6 @@
import * as express from 'express'
import 'express-validator'
-import { getHostWithPort } from '../helpers/utils'
+import { getHostWithPort } from '../helpers/express-utils'
function setBodyHostsPort (req: express.Request, res: express.Response, next: express.NextFunction) {
if (!req.body.hosts) return next()
diff --git a/server/middlewares/validators/webfinger.ts b/server/middlewares/validators/webfinger.ts
index 3dbec6e44..3b9645048 100644
--- a/server/middlewares/validators/webfinger.ts
+++ b/server/middlewares/validators/webfinger.ts
@@ -2,9 +2,9 @@ import * as express from 'express'
import { query } from 'express-validator/check'
import { isWebfingerResourceValid } from '../../helpers/custom-validators/webfinger'
import { logger } from '../../helpers/logger'
-import { getHostWithPort } from '../../helpers/utils'
import { ActorModel } from '../../models/activitypub/actor'
import { areValidationErrors } from './utils'
+import { getHostWithPort } from '../../helpers/express-utils'
const webfingerValidator = [
query('resource').custom(isWebfingerResourceValid).withMessage('Should have a valid webfinger resource'),
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index b0fff6526..2ad9c00dd 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -95,7 +95,33 @@ enum ScopeNames {
}
@Scopes({
- [ScopeNames.AVAILABLE_FOR_LIST]: (actorId: number, hideNSFW: boolean, filter?: VideoFilter, withFiles?: boolean) => {
+ [ScopeNames.AVAILABLE_FOR_LIST]: (actorId: number, hideNSFW: boolean, filter?: VideoFilter, withFiles?: boolean, accountId?: number) => {
+ const accountInclude = {
+ attributes: [ 'name' ],
+ model: AccountModel.unscoped(),
+ required: true,
+ where: {},
+ include: [
+ {
+ attributes: [ 'preferredUsername', 'url', 'serverId', 'avatarId' ],
+ model: ActorModel.unscoped(),
+ required: true,
+ where: VideoModel.buildActorWhereWithFilter(filter),
+ include: [
+ {
+ attributes: [ 'host' ],
+ model: ServerModel.unscoped(),
+ required: false
+ },
+ {
+ model: AvatarModel.unscoped(),
+ required: false
+ }
+ ]
+ }
+ ]
+ }
+
const query: IFindOptions = {
where: {
id: {
@@ -125,30 +151,7 @@ enum ScopeNames {
model: VideoChannelModel.unscoped(),
required: true,
include: [
- {
- attributes: [ 'name' ],
- model: AccountModel.unscoped(),
- required: true,
- include: [
- {
- attributes: [ 'preferredUsername', 'url', 'serverId', 'avatarId' ],
- model: ActorModel.unscoped(),
- required: true,
- where: VideoModel.buildActorWhereWithFilter(filter),
- include: [
- {
- attributes: [ 'host' ],
- model: ServerModel.unscoped(),
- required: false
- },
- {
- model: AvatarModel.unscoped(),
- required: false
- }
- ]
- }
- ]
- }
+ accountInclude
]
}
]
@@ -166,6 +169,12 @@ enum ScopeNames {
query.where['nsfw'] = false
}
+ if (accountId) {
+ accountInclude.where = {
+ id: accountId
+ }
+ }
+
return query
},
[ScopeNames.WITH_ACCOUNT_DETAILS]: {
@@ -688,7 +697,15 @@ export class VideoModel extends Model {
})
}
- static async listForApi (start: number, count: number, sort: string, hideNSFW: boolean, filter?: VideoFilter, withFiles = false) {
+ static async listForApi (
+ start: number,
+ count: number,
+ sort: string,
+ hideNSFW: boolean,
+ filter?: VideoFilter,
+ withFiles = false,
+ accountId?: number
+ ) {
const query = {
offset: start,
limit: count,
@@ -696,7 +713,7 @@ export class VideoModel extends Model {
}
const serverActor = await getServerActor()
- return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, hideNSFW, filter, withFiles ] })
+ return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, hideNSFW, filter, withFiles, accountId ] })
.findAndCountAll(query)
.then(({ rows, count }) => {
return {
@@ -879,8 +896,6 @@ export class VideoModel extends Model {
private static getLanguageLabel (id: string) {
let languageLabel = VIDEO_LANGUAGES[id]
- console.log(VIDEO_LANGUAGES)
- console.log(id)
if (!languageLabel) languageLabel = 'Unknown'
return languageLabel