From e8cb44090e654fda339506dccfcec7fea8722723 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 21 Feb 2018 16:44:18 +0100 Subject: [PATCH] Add links to comment mentions --- client/package.json | 1 + .../+video-watch/comment/linkifier.service.ts | 114 ++++++++++++++++++ .../comment/video-comment-add.component.ts | 8 +- .../comment/video-comment.component.scss | 7 +- .../comment/video-comment.component.ts | 31 +++-- .../videos/+video-watch/video-watch.module.ts | 2 + .../src/app/videos/shared/markdown.service.ts | 11 -- client/yarn.lock | 90 +++++++++++++- server/controllers/services.ts | 9 ++ server/helpers/custom-validators/accounts.ts | 11 ++ server/helpers/logger.ts | 2 +- server/middlewares/validators/account.ts | 23 +++- server/models/account/account.ts | 37 +++++- 13 files changed, 314 insertions(+), 32 deletions(-) create mode 100644 client/src/app/videos/+video-watch/comment/linkifier.service.ts 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 @@ "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 index 000000000..3f4072efd --- /dev/null +++ b/client/src/app/videos/+video-watch/comment/linkifier.service.ts @@ -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) + } +} 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 { 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(' ') + ' ' 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 @@ .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; + } } } 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 @@ 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,13 +93,26 @@ export class VideoCommentComponent implements OnInit, OnChanges { } private init () { - this.sanitizedCommentHTML = sanitizeHtml(this.comment.text, { - allowedTags: [ 'a', 'p', 'span', 'br' ], - allowedSchemes: [ 'http', 'https' ] - }) - // Convert possible markdown to html - this.sanitizedCommentHTML = this.markdownService.linkify(this.comment.text) + const html = this.linkifierService.linkify(this.comment.text) + + this.sanitizedCommentHTML = sanitizeHtml(html, { + allowedTags: [ 'a', 'p', 'span', 'br' ], + allowedSchemes: [ 'http', 'https' ], + allowedAttributes: { + 'a': [ 'href', 'class' ] + }, + transformTags: { + a: (tagName, attribs) => { + return { + tagName, + attribs: Object.assign(attribs, { + target: '_blank' + }) + } + } + } + }) this.newParentComments = this.parentComments.concat([ this.comment ]) } 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 @@ 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 ] }) 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' @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) { 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: 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" 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 @@ 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) +} 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) { return isAccountExist(promise, res) } +function isAccountNameWithHostExist (nameWithDomain: string, res: Response) { + const [ accountName, host ] = nameWithDomain.split('@') + + let promise: Bluebird + if (!host) promise = AccountModel.loadLocalByName(accountName) + else promise = AccountModel.loadLocalByNameAndHost(accountName, host) + + return isAccountExist(promise, res) +} + async function isAccountExist (p: Bluebird, res: Response) { const account = await p @@ -53,5 +63,6 @@ export { isAccountIdExist, isLocalAccountNameExist, isAccountDescriptionValid, + isAccountNameWithHostExist, isAccountNameValid } 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({ ) }), new winston.transports.Console({ - handleExcegiptions: true, + handleExceptions: true, humanReadableUnhandledException: true, format: winston.format.combine( 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 @@ 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 } 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 { static loadLocalByName (name: string) { const query = { where: { - name, [ Sequelize.Op.or ]: [ { userId: { @@ -170,7 +169,41 @@ export class AccountModel extends Model { } } ] - } + }, + 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)