aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-12-05 17:27:24 +0100
committerChocobozzz <me@florianbigard.com>2018-12-05 17:44:34 +0100
commit2feebf3e6afaad9ab80976d1557d3a7bcf94de03 (patch)
tree47df940d7d600cec5e08eb7715bdb5bdae085e59 /server
parent3b3b18203fe73e499bf8b49b15369710df95993e (diff)
downloadPeerTube-2feebf3e6afaad9ab80976d1557d3a7bcf94de03.tar.gz
PeerTube-2feebf3e6afaad9ab80976d1557d3a7bcf94de03.tar.zst
PeerTube-2feebf3e6afaad9ab80976d1557d3a7bcf94de03.zip
Add sitemap
Diffstat (limited to 'server')
-rw-r--r--server/controllers/bots.ts101
-rw-r--r--server/controllers/index.ts1
-rw-r--r--server/helpers/express-utils.ts4
-rw-r--r--server/initializers/constants.ts1
-rw-r--r--server/models/account/account.ts21
-rw-r--r--server/models/video/video-channel.ts21
-rw-r--r--server/tests/misc-endpoints.ts72
7 files changed, 218 insertions, 3 deletions
diff --git a/server/controllers/bots.ts b/server/controllers/bots.ts
new file mode 100644
index 000000000..b4eaccf9f
--- /dev/null
+++ b/server/controllers/bots.ts
@@ -0,0 +1,101 @@
1import * as express from 'express'
2import { asyncMiddleware } from '../middlewares'
3import { CONFIG, ROUTE_CACHE_LIFETIME } from '../initializers'
4import * as sitemapModule from 'sitemap'
5import { logger } from '../helpers/logger'
6import { VideoModel } from '../models/video/video'
7import { VideoChannelModel } from '../models/video/video-channel'
8import { AccountModel } from '../models/account/account'
9import { cacheRoute } from '../middlewares/cache'
10import { buildNSFWFilter } from '../helpers/express-utils'
11import { truncate } from 'lodash'
12
13const botsRouter = express.Router()
14
15// Special route that add OpenGraph and oEmbed tags
16// Do not use a template engine for a so little thing
17botsRouter.use('/sitemap.xml',
18 asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.SITEMAP)),
19 asyncMiddleware(getSitemap)
20)
21
22// ---------------------------------------------------------------------------
23
24export {
25 botsRouter
26}
27
28// ---------------------------------------------------------------------------
29
30async function getSitemap (req: express.Request, res: express.Response) {
31 let urls = getSitemapBasicUrls()
32
33 urls = urls.concat(await getSitemapLocalVideoUrls())
34 urls = urls.concat(await getSitemapVideoChannelUrls())
35 urls = urls.concat(await getSitemapAccountUrls())
36
37 const sitemap = sitemapModule.createSitemap({
38 hostname: CONFIG.WEBSERVER.URL,
39 urls: urls
40 })
41
42 sitemap.toXML((err, xml) => {
43 if (err) {
44 logger.error('Cannot generate sitemap.', { err })
45 return res.sendStatus(500)
46 }
47
48 res.header('Content-Type', 'application/xml')
49 res.send(xml)
50 })
51}
52
53async function getSitemapVideoChannelUrls () {
54 const rows = await VideoChannelModel.listLocalsForSitemap('createdAt')
55
56 return rows.map(channel => ({
57 url: CONFIG.WEBSERVER.URL + '/video-channels/' + channel.Actor.preferredUsername
58 }))
59}
60
61async function getSitemapAccountUrls () {
62 const rows = await AccountModel.listLocalsForSitemap('createdAt')
63
64 return rows.map(channel => ({
65 url: CONFIG.WEBSERVER.URL + '/accounts/' + channel.Actor.preferredUsername
66 }))
67}
68
69async function getSitemapLocalVideoUrls () {
70 const resultList = await VideoModel.listForApi({
71 start: 0,
72 count: undefined,
73 sort: 'createdAt',
74 includeLocalVideos: true,
75 nsfw: buildNSFWFilter(),
76 filter: 'local',
77 withFiles: false
78 })
79
80 return resultList.data.map(v => ({
81 url: CONFIG.WEBSERVER.URL + '/videos/watch/' + v.uuid,
82 video: [
83 {
84 title: v.name,
85 // Sitemap description should be < 2000 characters
86 description: truncate(v.description || v.name, { length: 2000, omission: '...' }),
87 player_loc: CONFIG.WEBSERVER.URL + '/videos/embed/' + v.uuid,
88 thumbnail_loc: v.getThumbnailStaticPath()
89 }
90 ]
91 }))
92}
93
94function getSitemapBasicUrls () {
95 const paths = [
96 '/about/instance',
97 '/videos/local'
98 ]
99
100 return paths.map(p => ({ url: CONFIG.WEBSERVER.URL + p }))
101}
diff --git a/server/controllers/index.ts b/server/controllers/index.ts
index 197fa897a..a88a03c79 100644
--- a/server/controllers/index.ts
+++ b/server/controllers/index.ts
@@ -6,3 +6,4 @@ export * from './services'
6export * from './static' 6export * from './static'
7export * from './webfinger' 7export * from './webfinger'
8export * from './tracker' 8export * from './tracker'
9export * from './bots'
diff --git a/server/helpers/express-utils.ts b/server/helpers/express-utils.ts
index 162fe2244..9a72ee96d 100644
--- a/server/helpers/express-utils.ts
+++ b/server/helpers/express-utils.ts
@@ -7,12 +7,12 @@ import { extname } from 'path'
7import { isArray } from './custom-validators/misc' 7import { isArray } from './custom-validators/misc'
8import { UserModel } from '../models/account/user' 8import { UserModel } from '../models/account/user'
9 9
10function buildNSFWFilter (res: express.Response, paramNSFW?: string) { 10function buildNSFWFilter (res?: express.Response, paramNSFW?: string) {
11 if (paramNSFW === 'true') return true 11 if (paramNSFW === 'true') return true
12 if (paramNSFW === 'false') return false 12 if (paramNSFW === 'false') return false
13 if (paramNSFW === 'both') return undefined 13 if (paramNSFW === 'both') return undefined
14 14
15 if (res.locals.oauth) { 15 if (res && res.locals.oauth) {
16 const user: UserModel = res.locals.oauth.token.User 16 const user: UserModel = res.locals.oauth.token.User
17 17
18 // User does not want NSFW videos 18 // User does not want NSFW videos
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 7195ae6c5..6b798875c 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -61,6 +61,7 @@ const OAUTH_LIFETIME = {
61const ROUTE_CACHE_LIFETIME = { 61const ROUTE_CACHE_LIFETIME = {
62 FEEDS: '15 minutes', 62 FEEDS: '15 minutes',
63 ROBOTS: '2 hours', 63 ROBOTS: '2 hours',
64 SITEMAP: '1 day',
64 SECURITYTXT: '2 hours', 65 SECURITYTXT: '2 hours',
65 NODEINFO: '10 minutes', 66 NODEINFO: '10 minutes',
66 DNT_POLICY: '1 week', 67 DNT_POLICY: '1 week',
diff --git a/server/models/account/account.ts b/server/models/account/account.ts
index 5a237d733..a99e9b1ad 100644
--- a/server/models/account/account.ts
+++ b/server/models/account/account.ts
@@ -241,6 +241,27 @@ export class AccountModel extends Model<AccountModel> {
241 }) 241 })
242 } 242 }
243 243
244 static listLocalsForSitemap (sort: string) {
245 const query = {
246 attributes: [ ],
247 offset: 0,
248 order: getSort(sort),
249 include: [
250 {
251 attributes: [ 'preferredUsername', 'serverId' ],
252 model: ActorModel.unscoped(),
253 where: {
254 serverId: null
255 }
256 }
257 ]
258 }
259
260 return AccountModel
261 .unscoped()
262 .findAll(query)
263 }
264
244 toFormattedJSON (): Account { 265 toFormattedJSON (): Account {
245 const actor = this.Actor.toFormattedJSON() 266 const actor = this.Actor.toFormattedJSON()
246 const account = { 267 const account = {
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index f4586917e..86bf0461a 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -233,6 +233,27 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
233 }) 233 })
234 } 234 }
235 235
236 static listLocalsForSitemap (sort: string) {
237 const query = {
238 attributes: [ ],
239 offset: 0,
240 order: getSort(sort),
241 include: [
242 {
243 attributes: [ 'preferredUsername', 'serverId' ],
244 model: ActorModel.unscoped(),
245 where: {
246 serverId: null
247 }
248 }
249 ]
250 }
251
252 return VideoChannelModel
253 .unscoped()
254 .findAll(query)
255 }
256
236 static searchForApi (options: { 257 static searchForApi (options: {
237 actorId: number 258 actorId: number
238 search: string 259 search: string
diff --git a/server/tests/misc-endpoints.ts b/server/tests/misc-endpoints.ts
index 8fab20971..b53803ee1 100644
--- a/server/tests/misc-endpoints.ts
+++ b/server/tests/misc-endpoints.ts
@@ -2,7 +2,18 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { flushTests, killallServers, makeGetRequest, runServer, ServerInfo } from './utils' 5import {
6 addVideoChannel,
7 createUser,
8 flushTests,
9 killallServers,
10 makeGetRequest,
11 runServer,
12 ServerInfo,
13 setAccessTokensToServers,
14 uploadVideo
15} from './utils'
16import { VideoPrivacy } from '../../shared/models/videos'
6 17
7const expect = chai.expect 18const expect = chai.expect
8 19
@@ -15,6 +26,7 @@ describe('Test misc endpoints', function () {
15 await flushTests() 26 await flushTests()
16 27
17 server = await runServer(1) 28 server = await runServer(1)
29 await setAccessTokensToServers([ server ])
18 }) 30 })
19 31
20 describe('Test a well known endpoints', function () { 32 describe('Test a well known endpoints', function () {
@@ -93,6 +105,64 @@ describe('Test misc endpoints', function () {
93 }) 105 })
94 }) 106 })
95 107
108 describe('Test bots endpoints', function () {
109
110 it('Should get the empty sitemap', async function () {
111 const res = await makeGetRequest({
112 url: server.url,
113 path: '/sitemap.xml',
114 statusCodeExpected: 200
115 })
116
117 expect(res.text).to.contain('xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"')
118 expect(res.text).to.contain('<url><loc>http://localhost:9001/about/instance</loc></url>')
119 })
120
121 it('Should get the empty cached sitemap', async function () {
122 const res = await makeGetRequest({
123 url: server.url,
124 path: '/sitemap.xml',
125 statusCodeExpected: 200
126 })
127
128 expect(res.text).to.contain('xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"')
129 expect(res.text).to.contain('<url><loc>http://localhost:9001/about/instance</loc></url>')
130 })
131
132 it('Should add videos, channel and accounts and get sitemap', async function () {
133 this.timeout(35000)
134
135 await uploadVideo(server.url, server.accessToken, { name: 'video 1', nsfw: false })
136 await uploadVideo(server.url, server.accessToken, { name: 'video 2', nsfw: false })
137 await uploadVideo(server.url, server.accessToken, { name: 'video 3', privacy: VideoPrivacy.PRIVATE })
138
139 await addVideoChannel(server.url, server.accessToken, { name: 'channel1', displayName: 'channel 1' })
140 await addVideoChannel(server.url, server.accessToken, { name: 'channel2', displayName: 'channel 2' })
141
142 await createUser(server.url, server.accessToken, 'user1', 'password')
143 await createUser(server.url, server.accessToken, 'user2', 'password')
144
145 const res = await makeGetRequest({
146 url: server.url,
147 path: '/sitemap.xml?t=1', // avoid using cache
148 statusCodeExpected: 200
149 })
150
151 expect(res.text).to.contain('xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"')
152 expect(res.text).to.contain('<url><loc>http://localhost:9001/about/instance</loc></url>')
153
154 expect(res.text).to.contain('<video:title><![CDATA[video 1]]></video:title>')
155 expect(res.text).to.contain('<video:title><![CDATA[video 2]]></video:title>')
156 expect(res.text).to.not.contain('<video:title><![CDATA[video 3]]></video:title>')
157
158 expect(res.text).to.contain('<url><loc>http://localhost:9001/video-channels/channel1</loc></url>')
159 expect(res.text).to.contain('<url><loc>http://localhost:9001/video-channels/channel2</loc></url>')
160
161 expect(res.text).to.contain('<url><loc>http://localhost:9001/accounts/user1</loc></url>')
162 expect(res.text).to.contain('<url><loc>http://localhost:9001/accounts/user2</loc></url>')
163 })
164 })
165
96 after(async function () { 166 after(async function () {
97 killallServers([ server ]) 167 killallServers([ server ])
98 }) 168 })