diff options
author | Chocobozzz <me@florianbigard.com> | 2023-05-11 16:16:27 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2023-05-11 16:16:27 +0200 |
commit | 85c20aaeb90ef0e0f44c377e62c323fde275cdde (patch) | |
tree | 79d09e1b6f1746398c2ea4f0279ac54fa2be96dd | |
parent | 823c34c07fc0df81110098ee1032e9d3ed70b662 (diff) | |
download | PeerTube-85c20aaeb90ef0e0f44c377e62c323fde275cdde.tar.gz PeerTube-85c20aaeb90ef0e0f44c377e62c323fde275cdde.tar.zst PeerTube-85c20aaeb90ef0e0f44c377e62c323fde275cdde.zip |
Set actor preferred name case insensitive
-rw-r--r-- | server/controllers/api/users/my-subscriptions.ts | 2 | ||||
-rw-r--r-- | server/initializers/constants.ts | 2 | ||||
-rw-r--r-- | server/initializers/migrations/0770-actor-preferred-username.ts | 44 | ||||
-rw-r--r-- | server/models/account/account-video-rate.ts | 6 | ||||
-rw-r--r-- | server/models/account/account.ts | 10 | ||||
-rw-r--r-- | server/models/actor/actor-follow.ts | 22 | ||||
-rw-r--r-- | server/models/actor/actor.ts | 36 | ||||
-rw-r--r-- | server/models/video/video-channel.ts | 19 |
8 files changed, 95 insertions, 46 deletions
diff --git a/server/controllers/api/users/my-subscriptions.ts b/server/controllers/api/users/my-subscriptions.ts index 6ba8ba597..6e2aa3711 100644 --- a/server/controllers/api/users/my-subscriptions.ts +++ b/server/controllers/api/users/my-subscriptions.ts | |||
@@ -99,7 +99,7 @@ async function areSubscriptionsExist (req: express.Request, res: express.Respons | |||
99 | const obj = results.find(r => { | 99 | const obj = results.find(r => { |
100 | const server = r.ActorFollowing.Server | 100 | const server = r.ActorFollowing.Server |
101 | 101 | ||
102 | return r.ActorFollowing.preferredUsername === sanitizedHandle.name && | 102 | return r.ActorFollowing.preferredUsername.toLowerCase() === sanitizedHandle.name.toLowerCase() && |
103 | ( | 103 | ( |
104 | (!server && !sanitizedHandle.host) || | 104 | (!server && !sanitizedHandle.host) || |
105 | (server.host === sanitizedHandle.host) | 105 | (server.host === sanitizedHandle.host) |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index adf24b73f..1dfc9fb27 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -27,7 +27,7 @@ import { CONFIG, registerConfigChangedHandler } from './config' | |||
27 | 27 | ||
28 | // --------------------------------------------------------------------------- | 28 | // --------------------------------------------------------------------------- |
29 | 29 | ||
30 | const LAST_MIGRATION_VERSION = 765 | 30 | const LAST_MIGRATION_VERSION = 770 |
31 | 31 | ||
32 | // --------------------------------------------------------------------------- | 32 | // --------------------------------------------------------------------------- |
33 | 33 | ||
diff --git a/server/initializers/migrations/0770-actor-preferred-username.ts b/server/initializers/migrations/0770-actor-preferred-username.ts new file mode 100644 index 000000000..217813f7f --- /dev/null +++ b/server/initializers/migrations/0770-actor-preferred-username.ts | |||
@@ -0,0 +1,44 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction | ||
5 | queryInterface: Sequelize.QueryInterface | ||
6 | sequelize: Sequelize.Sequelize | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | const { transaction } = utils | ||
10 | |||
11 | await utils.sequelize.query('drop index if exists "actor_preferred_username"', { transaction }) | ||
12 | await utils.sequelize.query('drop index if exists "actor_preferred_username_server_id"', { transaction }) | ||
13 | |||
14 | await utils.sequelize.query( | ||
15 | 'DELETE FROM "actor" v1 USING (' + | ||
16 | 'SELECT MIN(id) as id, lower("preferredUsername") AS "lowerPreferredUsername", "serverId" ' + | ||
17 | 'FROM "actor" ' + | ||
18 | 'GROUP BY "lowerPreferredUsername", "serverId" HAVING COUNT(*) > 1 AND "serverId" IS NOT NULL' + | ||
19 | ') v2 ' + | ||
20 | 'WHERE lower(v1."preferredUsername") = v2."lowerPreferredUsername" AND v1."serverId" = v2."serverId" AND v1.id <> v2.id', | ||
21 | { transaction } | ||
22 | ) | ||
23 | |||
24 | await utils.sequelize.query( | ||
25 | 'DELETE FROM "actor" v1 USING (' + | ||
26 | 'SELECT MIN(id) as id, lower("preferredUsername") AS "lowerPreferredUsername", "serverId" ' + | ||
27 | 'FROM "actor" ' + | ||
28 | 'GROUP BY "lowerPreferredUsername", "serverId" HAVING COUNT(*) > 1 AND "serverId" IS NULL' + | ||
29 | ') v2 ' + | ||
30 | 'WHERE lower(v1."preferredUsername") = v2."lowerPreferredUsername" AND v1."serverId" IS NULL AND v1.id <> v2.id', | ||
31 | { transaction } | ||
32 | ) | ||
33 | } | ||
34 | |||
35 | async function down (utils: { | ||
36 | queryInterface: Sequelize.QueryInterface | ||
37 | transaction: Sequelize.Transaction | ||
38 | }) { | ||
39 | } | ||
40 | |||
41 | export { | ||
42 | up, | ||
43 | down | ||
44 | } | ||
diff --git a/server/models/account/account-video-rate.ts b/server/models/account/account-video-rate.ts index 9e7ef4394..18ff07d53 100644 --- a/server/models/account/account-video-rate.ts +++ b/server/models/account/account-video-rate.ts | |||
@@ -189,8 +189,10 @@ export class AccountVideoRateModel extends Model<Partial<AttributesOnly<AccountV | |||
189 | model: ActorModel.unscoped(), | 189 | model: ActorModel.unscoped(), |
190 | required: true, | 190 | required: true, |
191 | where: { | 191 | where: { |
192 | preferredUsername: accountName, | 192 | [Op.and]: [ |
193 | serverId: null | 193 | ActorModel.wherePreferredUsername(accountName), |
194 | { serverId: null } | ||
195 | ] | ||
194 | } | 196 | } |
195 | } | 197 | } |
196 | ] | 198 | ] |
diff --git a/server/models/account/account.ts b/server/models/account/account.ts index 5bf29f45a..ec4e8d946 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts | |||
@@ -37,8 +37,8 @@ import { ActorImageModel } from '../actor/actor-image' | |||
37 | import { ApplicationModel } from '../application/application' | 37 | import { ApplicationModel } from '../application/application' |
38 | import { ServerModel } from '../server/server' | 38 | import { ServerModel } from '../server/server' |
39 | import { ServerBlocklistModel } from '../server/server-blocklist' | 39 | import { ServerBlocklistModel } from '../server/server-blocklist' |
40 | import { UserModel } from '../user/user' | ||
41 | import { buildSQLAttributes, getSort, throwIfNotValid } from '../shared' | 40 | import { buildSQLAttributes, getSort, throwIfNotValid } from '../shared' |
41 | import { UserModel } from '../user/user' | ||
42 | import { VideoModel } from '../video/video' | 42 | import { VideoModel } from '../video/video' |
43 | import { VideoChannelModel } from '../video/video-channel' | 43 | import { VideoChannelModel } from '../video/video-channel' |
44 | import { VideoCommentModel } from '../video/video-comment' | 44 | import { VideoCommentModel } from '../video/video-comment' |
@@ -296,9 +296,7 @@ export class AccountModel extends Model<Partial<AttributesOnly<AccountModel>>> { | |||
296 | { | 296 | { |
297 | model: ActorModel, | 297 | model: ActorModel, |
298 | required: true, | 298 | required: true, |
299 | where: { | 299 | where: ActorModel.wherePreferredUsername(name) |
300 | preferredUsername: name | ||
301 | } | ||
302 | } | 300 | } |
303 | ] | 301 | ] |
304 | } | 302 | } |
@@ -321,9 +319,7 @@ export class AccountModel extends Model<Partial<AttributesOnly<AccountModel>>> { | |||
321 | { | 319 | { |
322 | model: ActorModel, | 320 | model: ActorModel, |
323 | required: true, | 321 | required: true, |
324 | where: { | 322 | where: ActorModel.wherePreferredUsername(name), |
325 | preferredUsername: name | ||
326 | }, | ||
327 | include: [ | 323 | include: [ |
328 | { | 324 | { |
329 | model: ServerModel, | 325 | model: ServerModel, |
diff --git a/server/models/actor/actor-follow.ts b/server/models/actor/actor-follow.ts index 32e5d78b0..0f199d208 100644 --- a/server/models/actor/actor-follow.ts +++ b/server/models/actor/actor-follow.ts | |||
@@ -37,8 +37,8 @@ import { logger } from '../../helpers/logger' | |||
37 | import { ACTOR_FOLLOW_SCORE, CONSTRAINTS_FIELDS, FOLLOW_STATES, SERVER_ACTOR_NAME, SORTABLE_COLUMNS } from '../../initializers/constants' | 37 | import { ACTOR_FOLLOW_SCORE, CONSTRAINTS_FIELDS, FOLLOW_STATES, SERVER_ACTOR_NAME, SORTABLE_COLUMNS } from '../../initializers/constants' |
38 | import { AccountModel } from '../account/account' | 38 | import { AccountModel } from '../account/account' |
39 | import { ServerModel } from '../server/server' | 39 | import { ServerModel } from '../server/server' |
40 | import { doesExist } from '../shared/query' | ||
41 | import { buildSQLAttributes, createSafeIn, getSort, searchAttribute, throwIfNotValid } from '../shared' | 40 | import { buildSQLAttributes, createSafeIn, getSort, searchAttribute, throwIfNotValid } from '../shared' |
41 | import { doesExist } from '../shared/query' | ||
42 | import { VideoChannelModel } from '../video/video-channel' | 42 | import { VideoChannelModel } from '../video/video-channel' |
43 | import { ActorModel, unusedActorAttributesForAPI } from './actor' | 43 | import { ActorModel, unusedActorAttributesForAPI } from './actor' |
44 | import { InstanceListFollowersQueryBuilder, ListFollowersOptions } from './sql/instance-list-followers-query-builder' | 44 | import { InstanceListFollowersQueryBuilder, ListFollowersOptions } from './sql/instance-list-followers-query-builder' |
@@ -265,9 +265,7 @@ export class ActorFollowModel extends Model<Partial<AttributesOnly<ActorFollowMo | |||
265 | model: ActorModel, | 265 | model: ActorModel, |
266 | required: true, | 266 | required: true, |
267 | as: 'ActorFollowing', | 267 | as: 'ActorFollowing', |
268 | where: { | 268 | where: ActorModel.wherePreferredUsername(targetName), |
269 | preferredUsername: targetName | ||
270 | }, | ||
271 | include: [ | 269 | include: [ |
272 | { | 270 | { |
273 | model: VideoChannelModel.unscoped(), | 271 | model: VideoChannelModel.unscoped(), |
@@ -313,24 +311,16 @@ export class ActorFollowModel extends Model<Partial<AttributesOnly<ActorFollowMo | |||
313 | if (t.host) { | 311 | if (t.host) { |
314 | return { | 312 | return { |
315 | [Op.and]: [ | 313 | [Op.and]: [ |
316 | { | 314 | ActorModel.wherePreferredUsername(t.name, '$preferredUsername$'), |
317 | $preferredUsername$: t.name | 315 | { $host$: t.host } |
318 | }, | ||
319 | { | ||
320 | $host$: t.host | ||
321 | } | ||
322 | ] | 316 | ] |
323 | } | 317 | } |
324 | } | 318 | } |
325 | 319 | ||
326 | return { | 320 | return { |
327 | [Op.and]: [ | 321 | [Op.and]: [ |
328 | { | 322 | ActorModel.wherePreferredUsername(t.name, '$preferredUsername$'), |
329 | $preferredUsername$: t.name | 323 | { $serverId$: null } |
330 | }, | ||
331 | { | ||
332 | $serverId$: null | ||
333 | } | ||
334 | ] | 324 | ] |
335 | } | 325 | } |
336 | }) | 326 | }) |
diff --git a/server/models/actor/actor.ts b/server/models/actor/actor.ts index 1432e8757..1524e0533 100644 --- a/server/models/actor/actor.ts +++ b/server/models/actor/actor.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { literal, Op, QueryTypes, Transaction } from 'sequelize' | 1 | import { col, fn, literal, Op, QueryTypes, Transaction, where } from 'sequelize' |
2 | import { | 2 | import { |
3 | AllowNull, | 3 | AllowNull, |
4 | BelongsTo, | 4 | BelongsTo, |
@@ -130,7 +130,8 @@ export const unusedActorAttributesForAPI: (keyof AttributesOnly<ActorModel>)[] = | |||
130 | unique: true | 130 | unique: true |
131 | }, | 131 | }, |
132 | { | 132 | { |
133 | fields: [ 'preferredUsername', 'serverId' ], | 133 | fields: [ fn('lower', col('preferredUsername')), 'serverId' ], |
134 | name: 'actor_preferred_username_lower_server_id', | ||
134 | unique: true, | 135 | unique: true, |
135 | where: { | 136 | where: { |
136 | serverId: { | 137 | serverId: { |
@@ -139,7 +140,8 @@ export const unusedActorAttributesForAPI: (keyof AttributesOnly<ActorModel>)[] = | |||
139 | } | 140 | } |
140 | }, | 141 | }, |
141 | { | 142 | { |
142 | fields: [ 'preferredUsername' ], | 143 | fields: [ fn('lower', col('preferredUsername')) ], |
144 | name: 'actor_preferred_username_lower', | ||
143 | unique: true, | 145 | unique: true, |
144 | where: { | 146 | where: { |
145 | serverId: null | 147 | serverId: null |
@@ -327,6 +329,12 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> { | |||
327 | 329 | ||
328 | // --------------------------------------------------------------------------- | 330 | // --------------------------------------------------------------------------- |
329 | 331 | ||
332 | static wherePreferredUsername (preferredUsername: string, colName = 'preferredUsername') { | ||
333 | return where(fn('lower', col(colName)), preferredUsername.toLowerCase()) | ||
334 | } | ||
335 | |||
336 | // --------------------------------------------------------------------------- | ||
337 | |||
330 | static async load (id: number): Promise<MActor> { | 338 | static async load (id: number): Promise<MActor> { |
331 | const actorServer = await getServerActor() | 339 | const actorServer = await getServerActor() |
332 | if (id === actorServer.id) return actorServer | 340 | if (id === actorServer.id) return actorServer |
@@ -372,8 +380,12 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> { | |||
372 | const fun = () => { | 380 | const fun = () => { |
373 | const query = { | 381 | const query = { |
374 | where: { | 382 | where: { |
375 | preferredUsername, | 383 | [Op.and]: [ |
376 | serverId: null | 384 | this.wherePreferredUsername(preferredUsername), |
385 | { | ||
386 | serverId: null | ||
387 | } | ||
388 | ] | ||
377 | }, | 389 | }, |
378 | transaction | 390 | transaction |
379 | } | 391 | } |
@@ -395,8 +407,12 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> { | |||
395 | const query = { | 407 | const query = { |
396 | attributes: [ 'url' ], | 408 | attributes: [ 'url' ], |
397 | where: { | 409 | where: { |
398 | preferredUsername, | 410 | [Op.and]: [ |
399 | serverId: null | 411 | this.wherePreferredUsername(preferredUsername), |
412 | { | ||
413 | serverId: null | ||
414 | } | ||
415 | ] | ||
400 | }, | 416 | }, |
401 | transaction | 417 | transaction |
402 | } | 418 | } |
@@ -405,7 +421,7 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> { | |||
405 | } | 421 | } |
406 | 422 | ||
407 | return ModelCache.Instance.doCache({ | 423 | return ModelCache.Instance.doCache({ |
408 | cacheType: 'local-actor-name', | 424 | cacheType: 'local-actor-url', |
409 | key: preferredUsername, | 425 | key: preferredUsername, |
410 | // The server actor never change, so we can easily cache it | 426 | // The server actor never change, so we can easily cache it |
411 | whitelist: () => preferredUsername === SERVER_ACTOR_NAME, | 427 | whitelist: () => preferredUsername === SERVER_ACTOR_NAME, |
@@ -415,9 +431,7 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> { | |||
415 | 431 | ||
416 | static loadByNameAndHost (preferredUsername: string, host: string): Promise<MActorFull> { | 432 | static loadByNameAndHost (preferredUsername: string, host: string): Promise<MActorFull> { |
417 | const query = { | 433 | const query = { |
418 | where: { | 434 | where: this.wherePreferredUsername(preferredUsername), |
419 | preferredUsername | ||
420 | }, | ||
421 | include: [ | 435 | include: [ |
422 | { | 436 | { |
423 | model: ServerModel, | 437 | model: ServerModel, |
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index 67fccab68..306bc6ade 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts | |||
@@ -130,13 +130,16 @@ export type SummaryOptions = { | |||
130 | for (const handle of options.handles || []) { | 130 | for (const handle of options.handles || []) { |
131 | const [ preferredUsername, host ] = handle.split('@') | 131 | const [ preferredUsername, host ] = handle.split('@') |
132 | 132 | ||
133 | const sanitizedPreferredUsername = VideoChannelModel.sequelize.escape(preferredUsername.toLowerCase()) | ||
134 | const sanitizedHost = VideoChannelModel.sequelize.escape(host) | ||
135 | |||
133 | if (!host || host === WEBSERVER.HOST) { | 136 | if (!host || host === WEBSERVER.HOST) { |
134 | or.push(`("preferredUsername" = ${VideoChannelModel.sequelize.escape(preferredUsername)} AND "serverId" IS NULL)`) | 137 | or.push(`(LOWER("preferredUsername") = ${sanitizedPreferredUsername} AND "serverId" IS NULL)`) |
135 | } else { | 138 | } else { |
136 | or.push( | 139 | or.push( |
137 | `(` + | 140 | `(` + |
138 | `"preferredUsername" = ${VideoChannelModel.sequelize.escape(preferredUsername)} ` + | 141 | `LOWER("preferredUsername") = ${sanitizedPreferredUsername} ` + |
139 | `AND "host" = ${VideoChannelModel.sequelize.escape(host)}` + | 142 | `AND "host" = ${sanitizedHost}` + |
140 | `)` | 143 | `)` |
141 | ) | 144 | ) |
142 | } | 145 | } |
@@ -698,8 +701,10 @@ export class VideoChannelModel extends Model<Partial<AttributesOnly<VideoChannel | |||
698 | model: ActorModel, | 701 | model: ActorModel, |
699 | required: true, | 702 | required: true, |
700 | where: { | 703 | where: { |
701 | preferredUsername: name, | 704 | [Op.and]: [ |
702 | serverId: null | 705 | ActorModel.wherePreferredUsername(name), |
706 | { serverId: null } | ||
707 | ] | ||
703 | }, | 708 | }, |
704 | include: [ | 709 | include: [ |
705 | { | 710 | { |
@@ -723,9 +728,7 @@ export class VideoChannelModel extends Model<Partial<AttributesOnly<VideoChannel | |||
723 | { | 728 | { |
724 | model: ActorModel, | 729 | model: ActorModel, |
725 | required: true, | 730 | required: true, |
726 | where: { | 731 | where: ActorModel.wherePreferredUsername(name), |
727 | preferredUsername: name | ||
728 | }, | ||
729 | include: [ | 732 | include: [ |
730 | { | 733 | { |
731 | model: ServerModel, | 734 | model: ServerModel, |