aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-02-21 16:44:18 +0100
committerChocobozzz <me@florianbigard.com>2018-02-21 16:44:43 +0100
commite8cb44090e654fda339506dccfcec7fea8722723 (patch)
tree2a36d264cf44cededff0e272919316a3feabcbc1
parent276d03ed1a469fd4e3579f92392b6f9a1567d1ca (diff)
downloadPeerTube-e8cb44090e654fda339506dccfcec7fea8722723.tar.gz
PeerTube-e8cb44090e654fda339506dccfcec7fea8722723.tar.zst
PeerTube-e8cb44090e654fda339506dccfcec7fea8722723.zip
Add links to comment mentions
-rw-r--r--client/package.json1
-rw-r--r--client/src/app/videos/+video-watch/comment/linkifier.service.ts114
-rw-r--r--client/src/app/videos/+video-watch/comment/video-comment-add.component.ts8
-rw-r--r--client/src/app/videos/+video-watch/comment/video-comment.component.scss7
-rw-r--r--client/src/app/videos/+video-watch/comment/video-comment.component.ts29
-rw-r--r--client/src/app/videos/+video-watch/video-watch.module.ts2
-rw-r--r--client/src/app/videos/shared/markdown.service.ts11
-rw-r--r--client/yarn.lock90
-rw-r--r--server/controllers/services.ts9
-rw-r--r--server/helpers/custom-validators/accounts.ts11
-rw-r--r--server/helpers/logger.ts2
-rw-r--r--server/middlewares/validators/account.ts23
-rw-r--r--server/models/account/account.ts37
13 files changed, 313 insertions, 31 deletions
diff --git a/client/package.json b/client/package.json
index 6ef4d7050..cd6b727f0 100644
--- a/client/package.json
+++ b/client/package.json
@@ -57,6 +57,7 @@
57 "extract-text-webpack-plugin": "^3.0.2", 57 "extract-text-webpack-plugin": "^3.0.2",
58 "file-loader": "^1.1.5", 58 "file-loader": "^1.1.5",
59 "html-webpack-plugin": "^2.19.0", 59 "html-webpack-plugin": "^2.19.0",
60 "linkifyjs": "^2.1.5",
60 "lodash-es": "^4.17.4", 61 "lodash-es": "^4.17.4",
61 "markdown-it": "^8.4.0", 62 "markdown-it": "^8.4.0",
62 "ngx-bootstrap": "2.0.2", 63 "ngx-bootstrap": "2.0.2",
diff --git a/client/src/app/videos/+video-watch/comment/linkifier.service.ts b/client/src/app/videos/+video-watch/comment/linkifier.service.ts
new file mode 100644
index 000000000..3f4072efd
--- /dev/null
+++ b/client/src/app/videos/+video-watch/comment/linkifier.service.ts
@@ -0,0 +1,114 @@
1import { Injectable } from '@angular/core'
2import { getAbsoluteAPIUrl } from '@app/shared/misc/utils'
3import * as linkify from 'linkifyjs'
4import * as linkifyHtml from 'linkifyjs/html'
5
6@Injectable()
7export class LinkifierService {
8
9 static CLASSNAME = 'linkified'
10
11 private linkifyOptions = {
12 className: {
13 mention: LinkifierService.CLASSNAME + '-mention',
14 url: LinkifierService.CLASSNAME + '-url'
15 }
16 }
17
18 constructor () {
19 // Apply plugin
20 this.mentionWithDomainPlugin(linkify)
21 }
22
23 linkify (text: string) {
24 return linkifyHtml(text, this.linkifyOptions)
25 }
26
27 private mentionWithDomainPlugin (linkify: any) {
28 const TT = linkify.scanner.TOKENS // Text tokens
29 const { TOKENS: MT, State } = linkify.parser // Multi tokens, state
30 const MultiToken = MT.Base
31 const S_START = linkify.parser.start
32
33 const TT_AT = TT.AT
34 const TT_DOMAIN = TT.DOMAIN
35 const TT_LOCALHOST = TT.LOCALHOST
36 const TT_NUM = TT.NUM
37 const TT_COLON = TT.COLON
38 const TT_SLASH = TT.SLASH
39 const TT_TLD = TT.TLD
40 const TT_UNDERSCORE = TT.UNDERSCORE
41 const TT_DOT = TT.DOT
42
43 function MENTION (value) {
44 this.v = value
45 }
46
47 linkify.inherits(MultiToken, MENTION, {
48 type: 'mentionWithDomain',
49 isLink: true,
50 toHref () {
51 return getAbsoluteAPIUrl() + '/services/redirect/accounts/' + this.toString().substr(1)
52 }
53 })
54
55 const S_AT = S_START.jump(TT_AT) // @
56 const S_AT_SYMS = new State()
57 const S_MENTION = new State(MENTION)
58 const S_MENTION_DIVIDER = new State()
59 const S_MENTION_DIVIDER_SYMS = new State()
60
61 // @_,
62 S_AT.on(TT_UNDERSCORE, S_AT_SYMS)
63
64 // @_*
65 S_AT_SYMS
66 .on(TT_UNDERSCORE, S_AT_SYMS)
67 .on(TT_DOT, S_AT_SYMS)
68
69 // Valid mention (not made up entirely of symbols)
70 S_AT
71 .on(TT_DOMAIN, S_MENTION)
72 .on(TT_LOCALHOST, S_MENTION)
73 .on(TT_TLD, S_MENTION)
74 .on(TT_NUM, S_MENTION)
75
76 S_AT_SYMS
77 .on(TT_DOMAIN, S_MENTION)
78 .on(TT_LOCALHOST, S_MENTION)
79 .on(TT_TLD, S_MENTION)
80 .on(TT_NUM, S_MENTION)
81
82 // More valid mentions
83 S_MENTION
84 .on(TT_DOMAIN, S_MENTION)
85 .on(TT_LOCALHOST, S_MENTION)
86 .on(TT_TLD, S_MENTION)
87 .on(TT_COLON, S_MENTION)
88 .on(TT_NUM, S_MENTION)
89 .on(TT_UNDERSCORE, S_MENTION)
90
91 // Mention with a divider
92 S_MENTION
93 .on(TT_AT, S_MENTION_DIVIDER)
94 .on(TT_SLASH, S_MENTION_DIVIDER)
95 .on(TT_DOT, S_MENTION_DIVIDER)
96
97 // Mention _ trailing stash plus syms
98 S_MENTION_DIVIDER.on(TT_UNDERSCORE, S_MENTION_DIVIDER_SYMS)
99 S_MENTION_DIVIDER_SYMS.on(TT_UNDERSCORE, S_MENTION_DIVIDER_SYMS)
100
101 // Once we get a word token, mentions can start up again
102 S_MENTION_DIVIDER
103 .on(TT_DOMAIN, S_MENTION)
104 .on(TT_LOCALHOST, S_MENTION)
105 .on(TT_TLD, S_MENTION)
106 .on(TT_NUM, S_MENTION)
107
108 S_MENTION_DIVIDER_SYMS
109 .on(TT_DOMAIN, S_MENTION)
110 .on(TT_LOCALHOST, S_MENTION)
111 .on(TT_TLD, S_MENTION)
112 .on(TT_NUM, S_MENTION)
113 }
114}
diff --git a/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts b/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts
index 183cde000..e3f164b94 100644
--- a/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts
+++ b/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts
@@ -59,8 +59,12 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit {
59 59
60 if (this.parentComment) { 60 if (this.parentComment) {
61 const mentions = this.parentComments 61 const mentions = this.parentComments
62 .filter(c => c.account.id !== this.user.account.id) 62 .filter(c => c.account.id !== this.user.account.id) // Don't add mention of ourselves
63 .map(c => '@' + c.account.name) 63 .map(c => {
64 if (c.account.host) return '@' + c.account.name + '@' + c.account.host
65
66 return c.account.name
67 })
64 68
65 const mentionsSet = new Set(mentions) 69 const mentionsSet = new Set(mentions)
66 const mentionsText = Array.from(mentionsSet).join(' ') + ' ' 70 const mentionsText = Array.from(mentionsSet).join(' ') + ' '
diff --git a/client/src/app/videos/+video-watch/comment/video-comment.component.scss b/client/src/app/videos/+video-watch/comment/video-comment.component.scss
index d948c9670..afc6741b7 100644
--- a/client/src/app/videos/+video-watch/comment/video-comment.component.scss
+++ b/client/src/app/videos/+video-watch/comment/video-comment.component.scss
@@ -46,10 +46,15 @@
46 .comment-html { 46 .comment-html {
47 word-break: break-all; 47 word-break: break-all;
48 48
49 a { 49 /deep/ a {
50 @include disable-default-a-behaviour; 50 @include disable-default-a-behaviour;
51 51
52 color: #000; 52 color: #000;
53
54 // Semi bold mentions
55 &:not(.linkified-url) {
56 font-weight: $font-semibold;
57 }
53 } 58 }
54 } 59 }
55 60
diff --git a/client/src/app/videos/+video-watch/comment/video-comment.component.ts b/client/src/app/videos/+video-watch/comment/video-comment.component.ts
index 0224132ac..8f2d79ec1 100644
--- a/client/src/app/videos/+video-watch/comment/video-comment.component.ts
+++ b/client/src/app/videos/+video-watch/comment/video-comment.component.ts
@@ -1,5 +1,5 @@
1import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core' 1import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'
2import { MarkdownService } from '@app/videos/shared' 2import { LinkifierService } from '@app/videos/+video-watch/comment/linkifier.service'
3import * as sanitizeHtml from 'sanitize-html' 3import * as sanitizeHtml from 'sanitize-html'
4import { Account as AccountInterface } from '../../../../../../shared/models/actors' 4import { Account as AccountInterface } from '../../../../../../shared/models/actors'
5import { UserRight } from '../../../../../../shared/models/users' 5import { UserRight } from '../../../../../../shared/models/users'
@@ -31,8 +31,8 @@ export class VideoCommentComponent implements OnInit, OnChanges {
31 newParentComments = [] 31 newParentComments = []
32 32
33 constructor ( 33 constructor (
34 private authService: AuthService, 34 private linkifierService: LinkifierService,
35 private markdownService: MarkdownService 35 private authService: AuthService
36 ) {} 36 ) {}
37 37
38 get user () { 38 get user () {
@@ -93,14 +93,27 @@ export class VideoCommentComponent implements OnInit, OnChanges {
93 } 93 }
94 94
95 private init () { 95 private init () {
96 this.sanitizedCommentHTML = sanitizeHtml(this.comment.text, { 96 // Convert possible markdown to html
97 const html = this.linkifierService.linkify(this.comment.text)
98
99 this.sanitizedCommentHTML = sanitizeHtml(html, {
97 allowedTags: [ 'a', 'p', 'span', 'br' ], 100 allowedTags: [ 'a', 'p', 'span', 'br' ],
98 allowedSchemes: [ 'http', 'https' ] 101 allowedSchemes: [ 'http', 'https' ],
102 allowedAttributes: {
103 'a': [ 'href', 'class' ]
104 },
105 transformTags: {
106 a: (tagName, attribs) => {
107 return {
108 tagName,
109 attribs: Object.assign(attribs, {
110 target: '_blank'
111 })
112 }
113 }
114 }
99 }) 115 })
100 116
101 // Convert possible markdown to html
102 this.sanitizedCommentHTML = this.markdownService.linkify(this.comment.text)
103
104 this.newParentComments = this.parentComments.concat([ this.comment ]) 117 this.newParentComments = this.parentComments.concat([ this.comment ])
105 } 118 }
106} 119}
diff --git a/client/src/app/videos/+video-watch/video-watch.module.ts b/client/src/app/videos/+video-watch/video-watch.module.ts
index 6a22c36d9..63128926e 100644
--- a/client/src/app/videos/+video-watch/video-watch.module.ts
+++ b/client/src/app/videos/+video-watch/video-watch.module.ts
@@ -1,4 +1,5 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2import { LinkifierService } from '@app/videos/+video-watch/comment/linkifier.service'
2import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' 3import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component'
3import { TooltipModule } from 'ngx-bootstrap/tooltip' 4import { TooltipModule } from 'ngx-bootstrap/tooltip'
4import { ClipboardModule } from 'ngx-clipboard' 5import { ClipboardModule } from 'ngx-clipboard'
@@ -42,6 +43,7 @@ import { VideoWatchComponent } from './video-watch.component'
42 43
43 providers: [ 44 providers: [
44 MarkdownService, 45 MarkdownService,
46 LinkifierService,
45 VideoCommentService 47 VideoCommentService
46 ] 48 ]
47}) 49})
diff --git a/client/src/app/videos/shared/markdown.service.ts b/client/src/app/videos/shared/markdown.service.ts
index bd100f092..fdd0ec8d2 100644
--- a/client/src/app/videos/shared/markdown.service.ts
+++ b/client/src/app/videos/shared/markdown.service.ts
@@ -5,7 +5,6 @@ import * as MarkdownIt from 'markdown-it'
5@Injectable() 5@Injectable()
6export class MarkdownService { 6export class MarkdownService {
7 private textMarkdownIt: MarkdownIt.MarkdownIt 7 private textMarkdownIt: MarkdownIt.MarkdownIt
8 private linkifier: MarkdownIt.MarkdownIt
9 private enhancedMarkdownIt: MarkdownIt.MarkdownIt 8 private enhancedMarkdownIt: MarkdownIt.MarkdownIt
10 9
11 constructor () { 10 constructor () {
@@ -27,10 +26,6 @@ export class MarkdownService {
27 .enable('list') 26 .enable('list')
28 .enable('image') 27 .enable('image')
29 this.setTargetToLinks(this.enhancedMarkdownIt) 28 this.setTargetToLinks(this.enhancedMarkdownIt)
30
31 this.linkifier = new MarkdownIt('zero', { linkify: true })
32 .enable('linkify')
33 this.setTargetToLinks(this.linkifier)
34 } 29 }
35 30
36 textMarkdownToHTML (markdown: string) { 31 textMarkdownToHTML (markdown: string) {
@@ -45,12 +40,6 @@ export class MarkdownService {
45 return this.avoidTruncatedLinks(html) 40 return this.avoidTruncatedLinks(html)
46 } 41 }
47 42
48 linkify (text: string) {
49 const html = this.linkifier.render(text)
50
51 return this.avoidTruncatedLinks(html)
52 }
53
54 private setTargetToLinks (markdownIt: MarkdownIt.MarkdownIt) { 43 private setTargetToLinks (markdownIt: MarkdownIt.MarkdownIt) {
55 // Snippet from markdown-it documentation: https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md#renderer 44 // Snippet from markdown-it documentation: https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md#renderer
56 const defaultRender = markdownIt.renderer.rules.link_open || function (tokens, idx, options, env, self) { 45 const defaultRender = markdownIt.renderer.rules.link_open || function (tokens, idx, options, env, self) {
diff --git a/client/yarn.lock b/client/yarn.lock
index a27aacfcb..d9caa5408 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -1526,6 +1526,10 @@ copy-webpack-plugin@4.3.0, copy-webpack-plugin@^4.1.1:
1526 pify "^3.0.0" 1526 pify "^3.0.0"
1527 serialize-javascript "^1.4.0" 1527 serialize-javascript "^1.4.0"
1528 1528
1529core-js@^1.0.0:
1530 version "1.2.7"
1531 resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
1532
1529core-js@^2.4.0, core-js@^2.4.1: 1533core-js@^2.4.0, core-js@^2.4.1:
1530 version "2.5.3" 1534 version "2.5.3"
1531 resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e" 1535 resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e"
@@ -2098,6 +2102,12 @@ encodeurl@~1.0.1:
2098 version "1.0.2" 2102 version "1.0.2"
2099 resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 2103 resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
2100 2104
2105encoding@^0.1.11:
2106 version "0.1.12"
2107 resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
2108 dependencies:
2109 iconv-lite "~0.4.13"
2110
2101end-of-stream@^1.0.0, end-of-stream@^1.1.0: 2111end-of-stream@^1.0.0, end-of-stream@^1.1.0:
2102 version "1.4.1" 2112 version "1.4.1"
2103 resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" 2113 resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
@@ -2574,6 +2584,18 @@ faye-websocket@~0.11.0:
2574 dependencies: 2584 dependencies:
2575 websocket-driver ">=0.5.1" 2585 websocket-driver ">=0.5.1"
2576 2586
2587fbjs@^0.8.16:
2588 version "0.8.16"
2589 resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db"
2590 dependencies:
2591 core-js "^1.0.0"
2592 isomorphic-fetch "^2.1.1"
2593 loose-envify "^1.0.0"
2594 object-assign "^4.1.0"
2595 promise "^7.1.1"
2596 setimmediate "^1.0.5"
2597 ua-parser-js "^0.7.9"
2598
2577figures@^1.3.5: 2599figures@^1.3.5:
2578 version "1.7.0" 2600 version "1.7.0"
2579 resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" 2601 resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
@@ -3269,7 +3291,7 @@ https-browserify@^1.0.0:
3269 version "1.0.0" 3291 version "1.0.0"
3270 resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" 3292 resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
3271 3293
3272iconv-lite@0.4.19: 3294iconv-lite@0.4.19, iconv-lite@~0.4.13:
3273 version "0.4.19" 3295 version "0.4.19"
3274 resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" 3296 resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
3275 3297
@@ -3636,7 +3658,7 @@ is-resolvable@^1.0.0:
3636 version "1.1.0" 3658 version "1.1.0"
3637 resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" 3659 resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
3638 3660
3639is-stream@^1.1.0: 3661is-stream@^1.0.1, is-stream@^1.1.0:
3640 version "1.1.0" 3662 version "1.1.0"
3641 resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" 3663 resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
3642 3664
@@ -3684,6 +3706,13 @@ isobject@^3.0.0, isobject@^3.0.1:
3684 version "3.0.1" 3706 version "3.0.1"
3685 resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" 3707 resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
3686 3708
3709isomorphic-fetch@^2.1.1:
3710 version "2.2.1"
3711 resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
3712 dependencies:
3713 node-fetch "^1.0.1"
3714 whatwg-fetch ">=0.10.0"
3715
3687isstream@~0.1.2: 3716isstream@~0.1.2:
3688 version "0.1.2" 3717 version "0.1.2"
3689 resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" 3718 resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
@@ -3713,6 +3742,10 @@ istanbul-lib-instrument@^1.7.3:
3713 istanbul-lib-coverage "^1.1.1" 3742 istanbul-lib-coverage "^1.1.1"
3714 semver "^5.3.0" 3743 semver "^5.3.0"
3715 3744
3745jquery@>=1.9.0:
3746 version "3.3.1"
3747 resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca"
3748
3716js-base64@^2.1.8, js-base64@^2.1.9: 3749js-base64@^2.1.8, js-base64@^2.1.9:
3717 version "2.4.3" 3750 version "2.4.3"
3718 resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.3.tgz#2e545ec2b0f2957f41356510205214e98fad6582" 3751 resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.3.tgz#2e545ec2b0f2957f41356510205214e98fad6582"
@@ -3934,6 +3967,14 @@ linkify-it@^2.0.0:
3934 dependencies: 3967 dependencies:
3935 uc.micro "^1.0.1" 3968 uc.micro "^1.0.1"
3936 3969
3970linkifyjs@^2.1.5:
3971 version "2.1.5"
3972 resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-2.1.5.tgz#effc9f01e4aeafbbdbef21a45feab38b9516f93e"
3973 optionalDependencies:
3974 jquery ">=1.9.0"
3975 react ">=0.14.0"
3976 react-dom ">=0.14.0"
3977
3937load-ip-set@^1.2.7: 3978load-ip-set@^1.2.7:
3938 version "1.3.1" 3979 version "1.3.1"
3939 resolved "https://registry.yarnpkg.com/load-ip-set/-/load-ip-set-1.3.1.tgz#cfd050c6916e7ba0ca85d0b566e7854713eb495e" 3980 resolved "https://registry.yarnpkg.com/load-ip-set/-/load-ip-set-1.3.1.tgz#cfd050c6916e7ba0ca85d0b566e7854713eb495e"
@@ -4122,7 +4163,7 @@ longest@^1.0.1:
4122 version "1.0.1" 4163 version "1.0.1"
4123 resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" 4164 resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
4124 4165
4125loose-envify@^1.0.0: 4166loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1:
4126 version "1.3.1" 4167 version "1.3.1"
4127 resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" 4168 resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
4128 dependencies: 4169 dependencies:
@@ -4546,6 +4587,13 @@ node-abi@^2.1.1:
4546 dependencies: 4587 dependencies:
4547 semver "^5.4.1" 4588 semver "^5.4.1"
4548 4589
4590node-fetch@^1.0.1:
4591 version "1.7.3"
4592 resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
4593 dependencies:
4594 encoding "^0.1.11"
4595 is-stream "^1.0.1"
4596
4549node-forge@0.7.1: 4597node-forge@0.7.1:
4550 version "0.7.1" 4598 version "0.7.1"
4551 resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.1.tgz#9da611ea08982f4b94206b3beb4cc9665f20c300" 4599 resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.1.tgz#9da611ea08982f4b94206b3beb4cc9665f20c300"
@@ -5487,6 +5535,14 @@ promise@^7.1.1:
5487 dependencies: 5535 dependencies:
5488 asap "~2.0.3" 5536 asap "~2.0.3"
5489 5537
5538prop-types@^15.6.0:
5539 version "15.6.0"
5540 resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856"
5541 dependencies:
5542 fbjs "^0.8.16"
5543 loose-envify "^1.3.1"
5544 object-assign "^4.1.1"
5545
5490proxy-addr@~2.0.2: 5546proxy-addr@~2.0.2:
5491 version "2.0.2" 5547 version "2.0.2"
5492 resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec" 5548 resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec"
@@ -5665,6 +5721,24 @@ rc@^1.1.6, rc@^1.1.7:
5665 minimist "^1.2.0" 5721 minimist "^1.2.0"
5666 strip-json-comments "~2.0.1" 5722 strip-json-comments "~2.0.1"
5667 5723
5724react-dom@>=0.14.0:
5725 version "16.2.0"
5726 resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.2.0.tgz#69003178601c0ca19b709b33a83369fe6124c044"
5727 dependencies:
5728 fbjs "^0.8.16"
5729 loose-envify "^1.1.0"
5730 object-assign "^4.1.1"
5731 prop-types "^15.6.0"
5732
5733react@>=0.14.0:
5734 version "16.2.0"
5735 resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba"
5736 dependencies:
5737 fbjs "^0.8.16"
5738 loose-envify "^1.1.0"
5739 object-assign "^4.1.1"
5740 prop-types "^15.6.0"
5741
5668read-cache@^1.0.0: 5742read-cache@^1.0.0:
5669 version "1.0.0" 5743 version "1.0.0"
5670 resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" 5744 resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
@@ -6253,7 +6327,7 @@ set-value@^2.0.0:
6253 is-plain-object "^2.0.3" 6327 is-plain-object "^2.0.3"
6254 split-string "^3.0.1" 6328 split-string "^3.0.1"
6255 6329
6256setimmediate@^1.0.4: 6330setimmediate@^1.0.4, setimmediate@^1.0.5:
6257 version "1.0.5" 6331 version "1.0.5"
6258 resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" 6332 resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
6259 6333
@@ -7079,6 +7153,10 @@ typescript@2.6, typescript@~2.6.2:
7079 version "2.6.2" 7153 version "2.6.2"
7080 resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" 7154 resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4"
7081 7155
7156ua-parser-js@^0.7.9:
7157 version "0.7.17"
7158 resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac"
7159
7082uc.micro@^1.0.1, uc.micro@^1.0.3: 7160uc.micro@^1.0.1, uc.micro@^1.0.3:
7083 version "1.0.5" 7161 version "1.0.5"
7084 resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.5.tgz#0c65f15f815aa08b560a61ce8b4db7ffc3f45376" 7162 resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.5.tgz#0c65f15f815aa08b560a61ce8b4db7ffc3f45376"
@@ -7597,6 +7675,10 @@ webtorrent@^0.98.0:
7597 xtend "^4.0.1" 7675 xtend "^4.0.1"
7598 zero-fill "^2.2.3" 7676 zero-fill "^2.2.3"
7599 7677
7678whatwg-fetch@>=0.10.0:
7679 version "2.0.3"
7680 resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
7681
7600when@~3.6.x: 7682when@~3.6.x:
7601 version "3.6.4" 7683 version "3.6.4"
7602 resolved "https://registry.yarnpkg.com/when/-/when-3.6.4.tgz#473b517ec159e2b85005497a13983f095412e34e" 7684 resolved "https://registry.yarnpkg.com/when/-/when-3.6.4.tgz#473b517ec159e2b85005497a13983f095412e34e"
diff --git a/server/controllers/services.ts b/server/controllers/services.ts
index 3ac78a5df..c272edccd 100644
--- a/server/controllers/services.ts
+++ b/server/controllers/services.ts
@@ -1,6 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import { CONFIG, EMBED_SIZE, PREVIEWS_SIZE } from '../initializers' 2import { CONFIG, EMBED_SIZE, PREVIEWS_SIZE } from '../initializers'
3import { asyncMiddleware, oembedValidator } from '../middlewares' 3import { asyncMiddleware, oembedValidator } from '../middlewares'
4import { accountsNameWithHostGetValidator } from '../middlewares/validators'
4import { VideoModel } from '../models/video/video' 5import { VideoModel } from '../models/video/video'
5 6
6const servicesRouter = express.Router() 7const servicesRouter = express.Router()
@@ -9,6 +10,10 @@ servicesRouter.use('/oembed',
9 asyncMiddleware(oembedValidator), 10 asyncMiddleware(oembedValidator),
10 generateOEmbed 11 generateOEmbed
11) 12)
13servicesRouter.use('/redirect/accounts/:nameWithHost',
14 asyncMiddleware(accountsNameWithHostGetValidator),
15 redirectToAccountUrl
16)
12 17
13// --------------------------------------------------------------------------- 18// ---------------------------------------------------------------------------
14 19
@@ -62,3 +67,7 @@ function generateOEmbed (req: express.Request, res: express.Response, next: expr
62 67
63 return res.json(json) 68 return res.json(json)
64} 69}
70
71function redirectToAccountUrl (req: express.Request, res: express.Response, next: express.NextFunction) {
72 return res.redirect(res.locals.account.Actor.url)
73}
diff --git a/server/helpers/custom-validators/accounts.ts b/server/helpers/custom-validators/accounts.ts
index a46ffc162..cc8641d6b 100644
--- a/server/helpers/custom-validators/accounts.ts
+++ b/server/helpers/custom-validators/accounts.ts
@@ -31,6 +31,16 @@ function isLocalAccountNameExist (name: string, res: Response) {
31 return isAccountExist(promise, res) 31 return isAccountExist(promise, res)
32} 32}
33 33
34function isAccountNameWithHostExist (nameWithDomain: string, res: Response) {
35 const [ accountName, host ] = nameWithDomain.split('@')
36
37 let promise: Bluebird<AccountModel>
38 if (!host) promise = AccountModel.loadLocalByName(accountName)
39 else promise = AccountModel.loadLocalByNameAndHost(accountName, host)
40
41 return isAccountExist(promise, res)
42}
43
34async function isAccountExist (p: Bluebird<AccountModel>, res: Response) { 44async function isAccountExist (p: Bluebird<AccountModel>, res: Response) {
35 const account = await p 45 const account = await p
36 46
@@ -53,5 +63,6 @@ export {
53 isAccountIdExist, 63 isAccountIdExist,
54 isLocalAccountNameExist, 64 isLocalAccountNameExist,
55 isAccountDescriptionValid, 65 isAccountDescriptionValid,
66 isAccountNameWithHostExist,
56 isAccountNameValid 67 isAccountNameValid
57} 68}
diff --git a/server/helpers/logger.ts b/server/helpers/logger.ts
index 7d1d72f29..a4e5b58a4 100644
--- a/server/helpers/logger.ts
+++ b/server/helpers/logger.ts
@@ -59,7 +59,7 @@ const logger = new winston.createLogger({
59 ) 59 )
60 }), 60 }),
61 new winston.transports.Console({ 61 new winston.transports.Console({
62 handleExcegiptions: true, 62 handleExceptions: true,
63 humanReadableUnhandledException: true, 63 humanReadableUnhandledException: true,
64 format: winston.format.combine( 64 format: winston.format.combine(
65 timestampFormatter, 65 timestampFormatter,
diff --git a/server/middlewares/validators/account.ts b/server/middlewares/validators/account.ts
index ebc2fcf2d..0c4b7051d 100644
--- a/server/middlewares/validators/account.ts
+++ b/server/middlewares/validators/account.ts
@@ -1,6 +1,11 @@
1import * as express from 'express' 1import * as express from 'express'
2import { param } from 'express-validator/check' 2import { param } from 'express-validator/check'
3import { isAccountIdExist, isAccountNameValid, isLocalAccountNameExist } from '../../helpers/custom-validators/accounts' 3import {
4 isAccountIdExist,
5 isAccountNameValid,
6 isAccountNameWithHostExist,
7 isLocalAccountNameExist
8} from '../../helpers/custom-validators/accounts'
4import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' 9import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
5import { logger } from '../../helpers/logger' 10import { logger } from '../../helpers/logger'
6import { areValidationErrors } from './utils' 11import { areValidationErrors } from './utils'
@@ -31,9 +36,23 @@ const accountsGetValidator = [
31 } 36 }
32] 37]
33 38
39const accountsNameWithHostGetValidator = [
40 param('nameWithHost').exists().withMessage('Should have an account name with host'),
41
42 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
43 logger.debug('Checking accountsNameWithHostGetValidator parameters', { parameters: req.params })
44
45 if (areValidationErrors(req, res)) return
46 if (!await isAccountNameWithHostExist(req.params.nameWithHost, res)) return
47
48 return next()
49 }
50]
51
34// --------------------------------------------------------------------------- 52// ---------------------------------------------------------------------------
35 53
36export { 54export {
37 localAccountValidator, 55 localAccountValidator,
38 accountsGetValidator 56 accountsGetValidator,
57 accountsNameWithHostGetValidator
39} 58}
diff --git a/server/models/account/account.ts b/server/models/account/account.ts
index bc7595a0e..c5955ef3b 100644
--- a/server/models/account/account.ts
+++ b/server/models/account/account.ts
@@ -157,7 +157,6 @@ export class AccountModel extends Model<AccountModel> {
157 static loadLocalByName (name: string) { 157 static loadLocalByName (name: string) {
158 const query = { 158 const query = {
159 where: { 159 where: {
160 name,
161 [ Sequelize.Op.or ]: [ 160 [ Sequelize.Op.or ]: [
162 { 161 {
163 userId: { 162 userId: {
@@ -170,7 +169,41 @@ export class AccountModel extends Model<AccountModel> {
170 } 169 }
171 } 170 }
172 ] 171 ]
173 } 172 },
173 include: [
174 {
175 model: ActorModel,
176 required: true,
177 where: {
178 preferredUsername: name
179 }
180 }
181 ]
182 }
183
184 return AccountModel.findOne(query)
185 }
186
187 static loadLocalByNameAndHost (name: string, host: string) {
188 const query = {
189 include: [
190 {
191 model: ActorModel,
192 required: true,
193 where: {
194 preferredUsername: name
195 },
196 include: [
197 {
198 model: ServerModel,
199 required: true,
200 where: {
201 host
202 }
203 }
204 ]
205 }
206 ]
174 } 207 }
175 208
176 return AccountModel.findOne(query) 209 return AccountModel.findOne(query)