1
0
Fork 0

Set actor preferred name case insensitive

This commit is contained in:
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 obj = results.find(r => {
const server = r.ActorFollowing.Server const server = r.ActorFollowing.Server
return r.ActorFollowing.preferredUsername === sanitizedHandle.name && return r.ActorFollowing.preferredUsername.toLowerCase() === sanitizedHandle.name.toLowerCase() &&
( (
(!server && !sanitizedHandle.host) || (!server && !sanitizedHandle.host) ||
(server.host === 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(), model: ActorModel.unscoped(),
required: true, required: true,
where: { where: {
preferredUsername: accountName, [Op.and]: [
serverId: null ActorModel.wherePreferredUsername(accountName),
{ serverId: null }
]
} }
} }
] ]

View file

@ -37,8 +37,8 @@ import { ActorImageModel } from '../actor/actor-image'
import { ApplicationModel } from '../application/application' import { ApplicationModel } from '../application/application'
import { ServerModel } from '../server/server' import { ServerModel } from '../server/server'
import { ServerBlocklistModel } from '../server/server-blocklist' import { ServerBlocklistModel } from '../server/server-blocklist'
import { UserModel } from '../user/user'
import { buildSQLAttributes, getSort, throwIfNotValid } from '../shared' import { buildSQLAttributes, getSort, throwIfNotValid } from '../shared'
import { UserModel } from '../user/user'
import { VideoModel } from '../video/video' import { VideoModel } from '../video/video'
import { VideoChannelModel } from '../video/video-channel' import { VideoChannelModel } from '../video/video-channel'
import { VideoCommentModel } from '../video/video-comment' import { VideoCommentModel } from '../video/video-comment'
@ -296,9 +296,7 @@ export class AccountModel extends Model<Partial<AttributesOnly<AccountModel>>> {
{ {
model: ActorModel, model: ActorModel,
required: true, required: true,
where: { where: ActorModel.wherePreferredUsername(name)
preferredUsername: name
}
} }
] ]
} }
@ -321,9 +319,7 @@ export class AccountModel extends Model<Partial<AttributesOnly<AccountModel>>> {
{ {
model: ActorModel, model: ActorModel,
required: true, required: true,
where: { where: ActorModel.wherePreferredUsername(name),
preferredUsername: name
},
include: [ include: [
{ {
model: ServerModel, 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 { ACTOR_FOLLOW_SCORE, CONSTRAINTS_FIELDS, FOLLOW_STATES, SERVER_ACTOR_NAME, SORTABLE_COLUMNS } from '../../initializers/constants'
import { AccountModel } from '../account/account' import { AccountModel } from '../account/account'
import { ServerModel } from '../server/server' import { ServerModel } from '../server/server'
import { doesExist } from '../shared/query'
import { buildSQLAttributes, createSafeIn, getSort, searchAttribute, throwIfNotValid } from '../shared' import { buildSQLAttributes, createSafeIn, getSort, searchAttribute, throwIfNotValid } from '../shared'
import { doesExist } from '../shared/query'
import { VideoChannelModel } from '../video/video-channel' import { VideoChannelModel } from '../video/video-channel'
import { ActorModel, unusedActorAttributesForAPI } from './actor' import { ActorModel, unusedActorAttributesForAPI } from './actor'
import { InstanceListFollowersQueryBuilder, ListFollowersOptions } from './sql/instance-list-followers-query-builder' import { InstanceListFollowersQueryBuilder, ListFollowersOptions } from './sql/instance-list-followers-query-builder'
@ -265,9 +265,7 @@ export class ActorFollowModel extends Model<Partial<AttributesOnly<ActorFollowMo
model: ActorModel, model: ActorModel,
required: true, required: true,
as: 'ActorFollowing', as: 'ActorFollowing',
where: { where: ActorModel.wherePreferredUsername(targetName),
preferredUsername: targetName
},
include: [ include: [
{ {
model: VideoChannelModel.unscoped(), model: VideoChannelModel.unscoped(),
@ -313,24 +311,16 @@ export class ActorFollowModel extends Model<Partial<AttributesOnly<ActorFollowMo
if (t.host) { if (t.host) {
return { return {
[Op.and]: [ [Op.and]: [
{ ActorModel.wherePreferredUsername(t.name, '$preferredUsername$'),
$preferredUsername$: t.name { $host$: t.host }
},
{
$host$: t.host
}
] ]
} }
} }
return { return {
[Op.and]: [ [Op.and]: [
{ ActorModel.wherePreferredUsername(t.name, '$preferredUsername$'),
$preferredUsername$: t.name { $serverId$: null }
},
{
$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 { import {
AllowNull, AllowNull,
BelongsTo, BelongsTo,
@ -130,7 +130,8 @@ export const unusedActorAttributesForAPI: (keyof AttributesOnly<ActorModel>)[] =
unique: true unique: true
}, },
{ {
fields: [ 'preferredUsername', 'serverId' ], fields: [ fn('lower', col('preferredUsername')), 'serverId' ],
name: 'actor_preferred_username_lower_server_id',
unique: true, unique: true,
where: { where: {
serverId: { 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, unique: true,
where: { where: {
serverId: null 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> { static async load (id: number): Promise<MActor> {
const actorServer = await getServerActor() const actorServer = await getServerActor()
if (id === actorServer.id) return actorServer if (id === actorServer.id) return actorServer
@ -372,8 +380,12 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
const fun = () => { const fun = () => {
const query = { const query = {
where: { where: {
preferredUsername, [Op.and]: [
serverId: null this.wherePreferredUsername(preferredUsername),
{
serverId: null
}
]
}, },
transaction transaction
} }
@ -395,8 +407,12 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
const query = { const query = {
attributes: [ 'url' ], attributes: [ 'url' ],
where: { where: {
preferredUsername, [Op.and]: [
serverId: null this.wherePreferredUsername(preferredUsername),
{
serverId: null
}
]
}, },
transaction transaction
} }
@ -405,7 +421,7 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
} }
return ModelCache.Instance.doCache({ return ModelCache.Instance.doCache({
cacheType: 'local-actor-name', cacheType: 'local-actor-url',
key: preferredUsername, key: preferredUsername,
// The server actor never change, so we can easily cache it // The server actor never change, so we can easily cache it
whitelist: () => preferredUsername === SERVER_ACTOR_NAME, 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> { static loadByNameAndHost (preferredUsername: string, host: string): Promise<MActorFull> {
const query = { const query = {
where: { where: this.wherePreferredUsername(preferredUsername),
preferredUsername
},
include: [ include: [
{ {
model: ServerModel, model: ServerModel,

View file

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