Set actor preferred name case insensitive

pull/5817/head
Chocobozzz 2023-05-11 16:16:27 +02:00
parent 823c34c07f
commit 85c20aaeb9
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
8 changed files with 95 additions and 46 deletions

View File

@ -99,7 +99,7 @@ async function areSubscriptionsExist (req: express.Request, res: express.Respons
const obj = results.find(r => {
const server = r.ActorFollowing.Server
return r.ActorFollowing.preferredUsername === sanitizedHandle.name &&
return r.ActorFollowing.preferredUsername.toLowerCase() === sanitizedHandle.name.toLowerCase() &&
(
(!server && !sanitizedHandle.host) ||
(server.host === sanitizedHandle.host)

View File

@ -27,7 +27,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
// ---------------------------------------------------------------------------
const LAST_MIGRATION_VERSION = 765
const LAST_MIGRATION_VERSION = 770
// ---------------------------------------------------------------------------

View File

@ -0,0 +1,44 @@
import * as Sequelize from 'sequelize'
async function up (utils: {
transaction: Sequelize.Transaction
queryInterface: Sequelize.QueryInterface
sequelize: Sequelize.Sequelize
db: any
}): Promise<void> {
const { transaction } = utils
await utils.sequelize.query('drop index if exists "actor_preferred_username"', { transaction })
await utils.sequelize.query('drop index if exists "actor_preferred_username_server_id"', { transaction })
await utils.sequelize.query(
'DELETE FROM "actor" v1 USING (' +
'SELECT MIN(id) as id, lower("preferredUsername") AS "lowerPreferredUsername", "serverId" ' +
'FROM "actor" ' +
'GROUP BY "lowerPreferredUsername", "serverId" HAVING COUNT(*) > 1 AND "serverId" IS NOT NULL' +
') v2 ' +
'WHERE lower(v1."preferredUsername") = v2."lowerPreferredUsername" AND v1."serverId" = v2."serverId" AND v1.id <> v2.id',
{ transaction }
)
await utils.sequelize.query(
'DELETE FROM "actor" v1 USING (' +
'SELECT MIN(id) as id, lower("preferredUsername") AS "lowerPreferredUsername", "serverId" ' +
'FROM "actor" ' +
'GROUP BY "lowerPreferredUsername", "serverId" HAVING COUNT(*) > 1 AND "serverId" IS NULL' +
') v2 ' +
'WHERE lower(v1."preferredUsername") = v2."lowerPreferredUsername" AND v1."serverId" IS NULL AND v1.id <> v2.id',
{ transaction }
)
}
async function down (utils: {
queryInterface: Sequelize.QueryInterface
transaction: Sequelize.Transaction
}) {
}
export {
up,
down
}

View File

@ -189,8 +189,10 @@ export class AccountVideoRateModel extends Model<Partial<AttributesOnly<AccountV
model: ActorModel.unscoped(),
required: true,
where: {
preferredUsername: accountName,
serverId: null
[Op.and]: [
ActorModel.wherePreferredUsername(accountName),
{ serverId: null }
]
}
}
]

View File

@ -37,8 +37,8 @@ import { ActorImageModel } from '../actor/actor-image'
import { ApplicationModel } from '../application/application'
import { ServerModel } from '../server/server'
import { ServerBlocklistModel } from '../server/server-blocklist'
import { UserModel } from '../user/user'
import { buildSQLAttributes, getSort, throwIfNotValid } from '../shared'
import { UserModel } from '../user/user'
import { VideoModel } from '../video/video'
import { VideoChannelModel } from '../video/video-channel'
import { VideoCommentModel } from '../video/video-comment'
@ -296,9 +296,7 @@ export class AccountModel extends Model<Partial<AttributesOnly<AccountModel>>> {
{
model: ActorModel,
required: true,
where: {
preferredUsername: name
}
where: ActorModel.wherePreferredUsername(name)
}
]
}
@ -321,9 +319,7 @@ export class AccountModel extends Model<Partial<AttributesOnly<AccountModel>>> {
{
model: ActorModel,
required: true,
where: {
preferredUsername: name
},
where: ActorModel.wherePreferredUsername(name),
include: [
{
model: ServerModel,

View File

@ -37,8 +37,8 @@ import { logger } from '../../helpers/logger'
import { ACTOR_FOLLOW_SCORE, CONSTRAINTS_FIELDS, FOLLOW_STATES, SERVER_ACTOR_NAME, SORTABLE_COLUMNS } from '../../initializers/constants'
import { AccountModel } from '../account/account'
import { ServerModel } from '../server/server'
import { doesExist } from '../shared/query'
import { buildSQLAttributes, createSafeIn, getSort, searchAttribute, throwIfNotValid } from '../shared'
import { doesExist } from '../shared/query'
import { VideoChannelModel } from '../video/video-channel'
import { ActorModel, unusedActorAttributesForAPI } from './actor'
import { InstanceListFollowersQueryBuilder, ListFollowersOptions } from './sql/instance-list-followers-query-builder'
@ -265,9 +265,7 @@ export class ActorFollowModel extends Model<Partial<AttributesOnly<ActorFollowMo
model: ActorModel,
required: true,
as: 'ActorFollowing',
where: {
preferredUsername: targetName
},
where: ActorModel.wherePreferredUsername(targetName),
include: [
{
model: VideoChannelModel.unscoped(),
@ -313,24 +311,16 @@ export class ActorFollowModel extends Model<Partial<AttributesOnly<ActorFollowMo
if (t.host) {
return {
[Op.and]: [
{
$preferredUsername$: t.name
},
{
$host$: t.host
}
ActorModel.wherePreferredUsername(t.name, '$preferredUsername$'),
{ $host$: t.host }
]
}
}
return {
[Op.and]: [
{
$preferredUsername$: t.name
},
{
$serverId$: null
}
ActorModel.wherePreferredUsername(t.name, '$preferredUsername$'),
{ $serverId$: null }
]
}
})

View File

@ -1,4 +1,4 @@
import { literal, Op, QueryTypes, Transaction } from 'sequelize'
import { col, fn, literal, Op, QueryTypes, Transaction, where } from 'sequelize'
import {
AllowNull,
BelongsTo,
@ -130,7 +130,8 @@ export const unusedActorAttributesForAPI: (keyof AttributesOnly<ActorModel>)[] =
unique: true
},
{
fields: [ 'preferredUsername', 'serverId' ],
fields: [ fn('lower', col('preferredUsername')), 'serverId' ],
name: 'actor_preferred_username_lower_server_id',
unique: true,
where: {
serverId: {
@ -139,7 +140,8 @@ export const unusedActorAttributesForAPI: (keyof AttributesOnly<ActorModel>)[] =
}
},
{
fields: [ 'preferredUsername' ],
fields: [ fn('lower', col('preferredUsername')) ],
name: 'actor_preferred_username_lower',
unique: true,
where: {
serverId: null
@ -327,6 +329,12 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
// ---------------------------------------------------------------------------
static wherePreferredUsername (preferredUsername: string, colName = 'preferredUsername') {
return where(fn('lower', col(colName)), preferredUsername.toLowerCase())
}
// ---------------------------------------------------------------------------
static async load (id: number): Promise<MActor> {
const actorServer = await getServerActor()
if (id === actorServer.id) return actorServer
@ -372,8 +380,12 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
const fun = () => {
const query = {
where: {
preferredUsername,
serverId: null
[Op.and]: [
this.wherePreferredUsername(preferredUsername),
{
serverId: null
}
]
},
transaction
}
@ -395,8 +407,12 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
const query = {
attributes: [ 'url' ],
where: {
preferredUsername,
serverId: null
[Op.and]: [
this.wherePreferredUsername(preferredUsername),
{
serverId: null
}
]
},
transaction
}
@ -405,7 +421,7 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
}
return ModelCache.Instance.doCache({
cacheType: 'local-actor-name',
cacheType: 'local-actor-url',
key: preferredUsername,
// The server actor never change, so we can easily cache it
whitelist: () => preferredUsername === SERVER_ACTOR_NAME,
@ -415,9 +431,7 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
static loadByNameAndHost (preferredUsername: string, host: string): Promise<MActorFull> {
const query = {
where: {
preferredUsername
},
where: this.wherePreferredUsername(preferredUsername),
include: [
{
model: ServerModel,

View File

@ -130,13 +130,16 @@ export type SummaryOptions = {
for (const handle of options.handles || []) {
const [ preferredUsername, host ] = handle.split('@')
const sanitizedPreferredUsername = VideoChannelModel.sequelize.escape(preferredUsername.toLowerCase())
const sanitizedHost = VideoChannelModel.sequelize.escape(host)
if (!host || host === WEBSERVER.HOST) {
or.push(`("preferredUsername" = ${VideoChannelModel.sequelize.escape(preferredUsername)} AND "serverId" IS NULL)`)
or.push(`(LOWER("preferredUsername") = ${sanitizedPreferredUsername} AND "serverId" IS NULL)`)
} else {
or.push(
`(` +
`"preferredUsername" = ${VideoChannelModel.sequelize.escape(preferredUsername)} ` +
`AND "host" = ${VideoChannelModel.sequelize.escape(host)}` +
`LOWER("preferredUsername") = ${sanitizedPreferredUsername} ` +
`AND "host" = ${sanitizedHost}` +
`)`
)
}
@ -698,8 +701,10 @@ export class VideoChannelModel extends Model<Partial<AttributesOnly<VideoChannel
model: ActorModel,
required: true,
where: {
preferredUsername: name,
serverId: null
[Op.and]: [
ActorModel.wherePreferredUsername(name),
{ serverId: null }
]
},
include: [
{
@ -723,9 +728,7 @@ export class VideoChannelModel extends Model<Partial<AttributesOnly<VideoChannel
{
model: ActorModel,
required: true,
where: {
preferredUsername: name
},
where: ActorModel.wherePreferredUsername(name),
include: [
{
model: ServerModel,