Add links to comment mentions
authorChocobozzz <me@florianbigard.com>
Wed, 21 Feb 2018 15:44:18 +0000 (16:44 +0100)
committerChocobozzz <me@florianbigard.com>
Wed, 21 Feb 2018 15:44:43 +0000 (16:44 +0100)
13 files changed:
client/package.json
client/src/app/videos/+video-watch/comment/linkifier.service.ts [new file with mode: 0644]
client/src/app/videos/+video-watch/comment/video-comment-add.component.ts
client/src/app/videos/+video-watch/comment/video-comment.component.scss
client/src/app/videos/+video-watch/comment/video-comment.component.ts
client/src/app/videos/+video-watch/video-watch.module.ts
client/src/app/videos/shared/markdown.service.ts
client/yarn.lock
server/controllers/services.ts
server/helpers/custom-validators/accounts.ts
server/helpers/logger.ts
server/middlewares/validators/account.ts
server/models/account/account.ts

index 6ef4d70507c605c17d7d0772df466ec9f4182b31..cd6b727f0e60bfb602235f319300d5505ff4912c 100644 (file)
@@ -57,6 +57,7 @@
     "extract-text-webpack-plugin": "^3.0.2",
     "file-loader": "^1.1.5",
     "html-webpack-plugin": "^2.19.0",
+    "linkifyjs": "^2.1.5",
     "lodash-es": "^4.17.4",
     "markdown-it": "^8.4.0",
     "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 (file)
index 0000000..3f4072e
--- /dev/null
@@ -0,0 +1,114 @@
+import { Injectable } from '@angular/core'
+import { getAbsoluteAPIUrl } from '@app/shared/misc/utils'
+import * as linkify from 'linkifyjs'
+import * as linkifyHtml from 'linkifyjs/html'
+
+@Injectable()
+export class LinkifierService {
+
+  static CLASSNAME = 'linkified'
+
+  private linkifyOptions = {
+    className: {
+      mention: LinkifierService.CLASSNAME + '-mention',
+      url: LinkifierService.CLASSNAME + '-url'
+    }
+  }
+
+  constructor () {
+    // Apply plugin
+    this.mentionWithDomainPlugin(linkify)
+  }
+
+  linkify (text: string) {
+    return linkifyHtml(text, this.linkifyOptions)
+  }
+
+  private mentionWithDomainPlugin (linkify: any) {
+    const TT = linkify.scanner.TOKENS // Text tokens
+    const { TOKENS: MT, State } = linkify.parser // Multi tokens, state
+    const MultiToken = MT.Base
+    const S_START = linkify.parser.start
+
+    const TT_AT = TT.AT
+    const TT_DOMAIN = TT.DOMAIN
+    const TT_LOCALHOST = TT.LOCALHOST
+    const TT_NUM = TT.NUM
+    const TT_COLON = TT.COLON
+    const TT_SLASH = TT.SLASH
+    const TT_TLD = TT.TLD
+    const TT_UNDERSCORE = TT.UNDERSCORE
+    const TT_DOT = TT.DOT
+
+    function MENTION (value) {
+      this.v = value
+    }
+
+    linkify.inherits(MultiToken, MENTION, {
+      type: 'mentionWithDomain',
+      isLink: true,
+      toHref () {
+        return getAbsoluteAPIUrl() + '/services/redirect/accounts/' + this.toString().substr(1)
+      }
+    })
+
+    const S_AT = S_START.jump(TT_AT) // @
+    const S_AT_SYMS = new State()
+    const S_MENTION = new State(MENTION)
+    const S_MENTION_DIVIDER = new State()
+    const S_MENTION_DIVIDER_SYMS = new State()
+
+    // @_,
+    S_AT.on(TT_UNDERSCORE, S_AT_SYMS)
+
+    //  @_*
+    S_AT_SYMS
+      .on(TT_UNDERSCORE, S_AT_SYMS)
+      .on(TT_DOT, S_AT_SYMS)
+
+    // Valid mention (not made up entirely of symbols)
+    S_AT
+      .on(TT_DOMAIN, S_MENTION)
+      .on(TT_LOCALHOST, S_MENTION)
+      .on(TT_TLD, S_MENTION)
+      .on(TT_NUM, S_MENTION)
+
+    S_AT_SYMS
+      .on(TT_DOMAIN, S_MENTION)
+      .on(TT_LOCALHOST, S_MENTION)
+      .on(TT_TLD, S_MENTION)
+      .on(TT_NUM, S_MENTION)
+
+    // More valid mentions
+    S_MENTION
+      .on(TT_DOMAIN, S_MENTION)
+      .on(TT_LOCALHOST, S_MENTION)
+      .on(TT_TLD, S_MENTION)
+      .on(TT_COLON, S_MENTION)
+      .on(TT_NUM, S_MENTION)
+      .on(TT_UNDERSCORE, S_MENTION)
+
+    // Mention with a divider
+    S_MENTION
+      .on(TT_AT, S_MENTION_DIVIDER)
+      .on(TT_SLASH, S_MENTION_DIVIDER)
+      .on(TT_DOT, S_MENTION_DIVIDER)
+
+    // Mention _ trailing stash plus syms
+    S_MENTION_DIVIDER.on(TT_UNDERSCORE, S_MENTION_DIVIDER_SYMS)
+    S_MENTION_DIVIDER_SYMS.on(TT_UNDERSCORE, S_MENTION_DIVIDER_SYMS)
+
+    // Once we get a word token, mentions can start up again
+    S_MENTION_DIVIDER
+      .on(TT_DOMAIN, S_MENTION)
+      .on(TT_LOCALHOST, S_MENTION)
+      .on(TT_TLD, S_MENTION)
+      .on(TT_NUM, S_MENTION)
+
+    S_MENTION_DIVIDER_SYMS
+      .on(TT_DOMAIN, S_MENTION)
+      .on(TT_LOCALHOST, S_MENTION)
+      .on(TT_TLD, S_MENTION)
+      .on(TT_NUM, S_MENTION)
+  }
+}
index 183cde0002e3df08a244ca6688d9a8f934cbefd7..e3f164b94536e687f722cb48e19a8043f89ad100 100644 (file)
@@ -59,8 +59,12 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit {
 
     if (this.parentComment) {
       const mentions = this.parentComments
-        .filter(c => c.account.id !== this.user.account.id)
-        .map(c => '@' + c.account.name)
+        .filter(c => c.account.id !== this.user.account.id) // Don't add mention of ourselves
+        .map(c => {
+          if (c.account.host) return '@' + c.account.name + '@' + c.account.host
+
+          return c.account.name
+        })
 
       const mentionsSet = new Set(mentions)
       const mentionsText = Array.from(mentionsSet).join(' ') + ' '
index d948c967055eb566e9ae17f1c8f10dec699186b4..afc6741b74cf7e061043cc338c4a11373bb70b9c 100644 (file)
     .comment-html {
       word-break: break-all;
 
-      a {
+      /deep/ a {
         @include disable-default-a-behaviour;
 
         color: #000;
+
+        // Semi bold mentions
+        &:not(.linkified-url) {
+          font-weight: $font-semibold;
+        }
       }
     }
 
index 0224132ac9b53cee3a5f449de3310aee9b3463bf..8f2d79ec1c405010ae3c64468328f5729360d3ea 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'
-import { MarkdownService } from '@app/videos/shared'
+import { LinkifierService } from '@app/videos/+video-watch/comment/linkifier.service'
 import * as sanitizeHtml from 'sanitize-html'
 import { Account as AccountInterface } from '../../../../../../shared/models/actors'
 import { UserRight } from '../../../../../../shared/models/users'
@@ -31,8 +31,8 @@ export class VideoCommentComponent implements OnInit, OnChanges {
   newParentComments = []
 
   constructor (
-    private authService: AuthService,
-    private markdownService: MarkdownService
+    private linkifierService: LinkifierService,
+    private authService: AuthService
   ) {}
 
   get user () {
@@ -93,14 +93,27 @@ export class VideoCommentComponent implements OnInit, OnChanges {
   }
 
   private init () {
-    this.sanitizedCommentHTML = sanitizeHtml(this.comment.text, {
+    // Convert possible markdown to html
+    const html = this.linkifierService.linkify(this.comment.text)
+
+    this.sanitizedCommentHTML = sanitizeHtml(html, {
       allowedTags: [ 'a', 'p', 'span', 'br' ],
-      allowedSchemes: [ 'http', 'https' ]
+      allowedSchemes: [ 'http', 'https' ],
+      allowedAttributes: {
+        'a': [ 'href', 'class' ]
+      },
+      transformTags: {
+        a: (tagName, attribs) => {
+          return {
+            tagName,
+            attribs: Object.assign(attribs, {
+              target: '_blank'
+            })
+          }
+        }
+      }
     })
 
-    // Convert possible markdown to html
-    this.sanitizedCommentHTML = this.markdownService.linkify(this.comment.text)
-
     this.newParentComments = this.parentComments.concat([ this.comment ])
   }
 }
index 6a22c36d9ae66e95ad9f68572830dcea9bd81f32..63128926e02a0e8a667c6288714e2efa60d5e514 100644 (file)
@@ -1,4 +1,5 @@
 import { NgModule } from '@angular/core'
+import { LinkifierService } from '@app/videos/+video-watch/comment/linkifier.service'
 import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component'
 import { TooltipModule } from 'ngx-bootstrap/tooltip'
 import { ClipboardModule } from 'ngx-clipboard'
@@ -42,6 +43,7 @@ import { VideoWatchComponent } from './video-watch.component'
 
   providers: [
     MarkdownService,
+    LinkifierService,
     VideoCommentService
   ]
 })
index bd100f0926dcc88b95998f1e1e606dfa27aed3e1..fdd0ec8d2786364ab357afdb7155b7746d5e1acb 100644 (file)
@@ -5,7 +5,6 @@ import * as MarkdownIt from 'markdown-it'
 @Injectable()
 export class MarkdownService {
   private textMarkdownIt: MarkdownIt.MarkdownIt
-  private linkifier: MarkdownIt.MarkdownIt
   private enhancedMarkdownIt: MarkdownIt.MarkdownIt
 
   constructor () {
@@ -27,10 +26,6 @@ export class MarkdownService {
       .enable('list')
       .enable('image')
     this.setTargetToLinks(this.enhancedMarkdownIt)
-
-    this.linkifier = new MarkdownIt('zero', { linkify: true })
-      .enable('linkify')
-    this.setTargetToLinks(this.linkifier)
   }
 
   textMarkdownToHTML (markdown: string) {
@@ -45,12 +40,6 @@ export class MarkdownService {
     return this.avoidTruncatedLinks(html)
   }
 
-  linkify (text: string) {
-    const html = this.linkifier.render(text)
-
-    return this.avoidTruncatedLinks(html)
-  }
-
   private setTargetToLinks (markdownIt: MarkdownIt.MarkdownIt) {
     // Snippet from markdown-it documentation: https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md#renderer
     const defaultRender = markdownIt.renderer.rules.link_open || function (tokens, idx, options, env, self) {
index a27aacfcbe003c842de97b1e18856166b00d5267..d9caa5408860f8d4f411b579531c3fd563d90b67 100644 (file)
@@ -1526,6 +1526,10 @@ copy-webpack-plugin@4.3.0, copy-webpack-plugin@^4.1.1:
     pify "^3.0.0"
     serialize-javascript "^1.4.0"
 
+core-js@^1.0.0:
+  version "1.2.7"
+  resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
+
 core-js@^2.4.0, core-js@^2.4.1:
   version "2.5.3"
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e"
@@ -2098,6 +2102,12 @@ encodeurl@~1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
 
+encoding@^0.1.11:
+  version "0.1.12"
+  resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
+  dependencies:
+    iconv-lite "~0.4.13"
+
 end-of-stream@^1.0.0, end-of-stream@^1.1.0:
   version "1.4.1"
   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:
   dependencies:
     websocket-driver ">=0.5.1"
 
+fbjs@^0.8.16:
+  version "0.8.16"
+  resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db"
+  dependencies:
+    core-js "^1.0.0"
+    isomorphic-fetch "^2.1.1"
+    loose-envify "^1.0.0"
+    object-assign "^4.1.0"
+    promise "^7.1.1"
+    setimmediate "^1.0.5"
+    ua-parser-js "^0.7.9"
+
 figures@^1.3.5:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
@@ -3269,7 +3291,7 @@ https-browserify@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
 
-iconv-lite@0.4.19:
+iconv-lite@0.4.19, iconv-lite@~0.4.13:
   version "0.4.19"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
 
@@ -3636,7 +3658,7 @@ is-resolvable@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
 
-is-stream@^1.1.0:
+is-stream@^1.0.1, is-stream@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
 
@@ -3684,6 +3706,13 @@ isobject@^3.0.0, isobject@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
 
+isomorphic-fetch@^2.1.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
+  dependencies:
+    node-fetch "^1.0.1"
+    whatwg-fetch ">=0.10.0"
+
 isstream@~0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
@@ -3713,6 +3742,10 @@ istanbul-lib-instrument@^1.7.3:
     istanbul-lib-coverage "^1.1.1"
     semver "^5.3.0"
 
+jquery@>=1.9.0:
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca"
+
 js-base64@^2.1.8, js-base64@^2.1.9:
   version "2.4.3"
   resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.3.tgz#2e545ec2b0f2957f41356510205214e98fad6582"
@@ -3934,6 +3967,14 @@ linkify-it@^2.0.0:
   dependencies:
     uc.micro "^1.0.1"
 
+linkifyjs@^2.1.5:
+  version "2.1.5"
+  resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-2.1.5.tgz#effc9f01e4aeafbbdbef21a45feab38b9516f93e"
+  optionalDependencies:
+    jquery ">=1.9.0"
+    react ">=0.14.0"
+    react-dom ">=0.14.0"
+
 load-ip-set@^1.2.7:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/load-ip-set/-/load-ip-set-1.3.1.tgz#cfd050c6916e7ba0ca85d0b566e7854713eb495e"
@@ -4122,7 +4163,7 @@ longest@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
 
-loose-envify@^1.0.0:
+loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
   dependencies:
@@ -4546,6 +4587,13 @@ node-abi@^2.1.1:
   dependencies:
     semver "^5.4.1"
 
+node-fetch@^1.0.1:
+  version "1.7.3"
+  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
+  dependencies:
+    encoding "^0.1.11"
+    is-stream "^1.0.1"
+
 node-forge@0.7.1:
   version "0.7.1"
   resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.1.tgz#9da611ea08982f4b94206b3beb4cc9665f20c300"
@@ -5487,6 +5535,14 @@ promise@^7.1.1:
   dependencies:
     asap "~2.0.3"
 
+prop-types@^15.6.0:
+  version "15.6.0"
+  resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856"
+  dependencies:
+    fbjs "^0.8.16"
+    loose-envify "^1.3.1"
+    object-assign "^4.1.1"
+
 proxy-addr@~2.0.2:
   version "2.0.2"
   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:
     minimist "^1.2.0"
     strip-json-comments "~2.0.1"
 
+react-dom@>=0.14.0:
+  version "16.2.0"
+  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.2.0.tgz#69003178601c0ca19b709b33a83369fe6124c044"
+  dependencies:
+    fbjs "^0.8.16"
+    loose-envify "^1.1.0"
+    object-assign "^4.1.1"
+    prop-types "^15.6.0"
+
+react@>=0.14.0:
+  version "16.2.0"
+  resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba"
+  dependencies:
+    fbjs "^0.8.16"
+    loose-envify "^1.1.0"
+    object-assign "^4.1.1"
+    prop-types "^15.6.0"
+
 read-cache@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
@@ -6253,7 +6327,7 @@ set-value@^2.0.0:
     is-plain-object "^2.0.3"
     split-string "^3.0.1"
 
-setimmediate@^1.0.4:
+setimmediate@^1.0.4, setimmediate@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
 
@@ -7079,6 +7153,10 @@ typescript@2.6, typescript@~2.6.2:
   version "2.6.2"
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4"
 
+ua-parser-js@^0.7.9:
+  version "0.7.17"
+  resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac"
+
 uc.micro@^1.0.1, uc.micro@^1.0.3:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.5.tgz#0c65f15f815aa08b560a61ce8b4db7ffc3f45376"
@@ -7597,6 +7675,10 @@ webtorrent@^0.98.0:
     xtend "^4.0.1"
     zero-fill "^2.2.3"
 
+whatwg-fetch@>=0.10.0:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
+
 when@~3.6.x:
   version "3.6.4"
   resolved "https://registry.yarnpkg.com/when/-/when-3.6.4.tgz#473b517ec159e2b85005497a13983f095412e34e"
index 3ac78a5df7c0149e2d2de68d9f0ed2ff8a771bf3..c272edccd5196256d93089544d7388e2f11cfd90 100644 (file)
@@ -1,6 +1,7 @@
 import * as express from 'express'
 import { CONFIG, EMBED_SIZE, PREVIEWS_SIZE } from '../initializers'
 import { asyncMiddleware, oembedValidator } from '../middlewares'
+import { accountsNameWithHostGetValidator } from '../middlewares/validators'
 import { VideoModel } from '../models/video/video'
 
 const servicesRouter = express.Router()
@@ -9,6 +10,10 @@ servicesRouter.use('/oembed',
   asyncMiddleware(oembedValidator),
   generateOEmbed
 )
+servicesRouter.use('/redirect/accounts/:nameWithHost',
+  asyncMiddleware(accountsNameWithHostGetValidator),
+  redirectToAccountUrl
+)
 
 // ---------------------------------------------------------------------------
 
@@ -62,3 +67,7 @@ function generateOEmbed (req: express.Request, res: express.Response, next: expr
 
   return res.json(json)
 }
+
+function redirectToAccountUrl (req: express.Request, res: express.Response, next: express.NextFunction) {
+  return res.redirect(res.locals.account.Actor.url)
+}
index a46ffc1622ea2403eb6218eb33ed79712b427ea7..cc8641d6b40912f316823ae8014fee6c69daa9af 100644 (file)
@@ -31,6 +31,16 @@ function isLocalAccountNameExist (name: string, res: Response) {
   return isAccountExist(promise, res)
 }
 
+function isAccountNameWithHostExist (nameWithDomain: string, res: Response) {
+  const [ accountName, host ] = nameWithDomain.split('@')
+
+  let promise: Bluebird<AccountModel>
+  if (!host) promise = AccountModel.loadLocalByName(accountName)
+  else promise = AccountModel.loadLocalByNameAndHost(accountName, host)
+
+  return isAccountExist(promise, res)
+}
+
 async function isAccountExist (p: Bluebird<AccountModel>, res: Response) {
   const account = await p
 
@@ -53,5 +63,6 @@ export {
   isAccountIdExist,
   isLocalAccountNameExist,
   isAccountDescriptionValid,
+  isAccountNameWithHostExist,
   isAccountNameValid
 }
index 7d1d72f29d8b1d1fae4b858304777ecf1b11a152..a4e5b58a4e62c4985e218f916faffc082288370d 100644 (file)
@@ -59,7 +59,7 @@ const logger = new winston.createLogger({
       )
     }),
     new winston.transports.Console({
-      handleExcegiptions: true,
+      handleExceptions: true,
       humanReadableUnhandledException: true,
       format: winston.format.combine(
         timestampFormatter,
index ebc2fcf2d89099c72e3effb1f36046a7301fc352..0c4b7051dc51e14bc5fcf5df059976e013792b0c 100644 (file)
@@ -1,6 +1,11 @@
 import * as express from 'express'
 import { param } from 'express-validator/check'
-import { isAccountIdExist, isAccountNameValid, isLocalAccountNameExist } from '../../helpers/custom-validators/accounts'
+import {
+  isAccountIdExist,
+  isAccountNameValid,
+  isAccountNameWithHostExist,
+  isLocalAccountNameExist
+} from '../../helpers/custom-validators/accounts'
 import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
 import { logger } from '../../helpers/logger'
 import { areValidationErrors } from './utils'
@@ -31,9 +36,23 @@ const accountsGetValidator = [
   }
 ]
 
+const accountsNameWithHostGetValidator = [
+  param('nameWithHost').exists().withMessage('Should have an account name with host'),
+
+  async (req: express.Request, res: express.Response, next: express.NextFunction) => {
+    logger.debug('Checking accountsNameWithHostGetValidator parameters', { parameters: req.params })
+
+    if (areValidationErrors(req, res)) return
+    if (!await isAccountNameWithHostExist(req.params.nameWithHost, res)) return
+
+    return next()
+  }
+]
+
 // ---------------------------------------------------------------------------
 
 export {
   localAccountValidator,
-  accountsGetValidator
+  accountsGetValidator,
+  accountsNameWithHostGetValidator
 }
index bc7595a0ee63f07d393b2b20b35034b2f5e4e4a3..c5955ef3b3096d26c7da6e941c75c44b725915ac 100644 (file)
@@ -157,7 +157,6 @@ export class AccountModel extends Model<AccountModel> {
   static loadLocalByName (name: string) {
     const query = {
       where: {
-        name,
         [ Sequelize.Op.or ]: [
           {
             userId: {
@@ -170,7 +169,41 @@ export class AccountModel extends Model<AccountModel> {
             }
           }
         ]
-      }
+      },
+      include: [
+        {
+          model: ActorModel,
+          required: true,
+          where: {
+            preferredUsername: name
+          }
+        }
+      ]
+    }
+
+    return AccountModel.findOne(query)
+  }
+
+  static loadLocalByNameAndHost (name: string, host: string) {
+    const query = {
+      include: [
+        {
+          model: ActorModel,
+          required: true,
+          where: {
+            preferredUsername: name
+          },
+          include: [
+            {
+              model: ServerModel,
+              required: true,
+              where: {
+                host
+              }
+            }
+          ]
+        }
+      ]
     }
 
     return AccountModel.findOne(query)