Set actor preferred name case insensitive
This commit is contained in:
parent
823c34c07f
commit
85c20aaeb9
8 changed files with 95 additions and 46 deletions
|
@ -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)
|
||||||
|
|
|
@ -27,7 +27,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const LAST_MIGRATION_VERSION = 765
|
const LAST_MIGRATION_VERSION = 770
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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 }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue