account-blocklist.ts 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. import { FindOptions, Op, QueryTypes } from 'sequelize'
  2. import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
  3. import { handlesToNameAndHost } from '@server/helpers/actors'
  4. import { MAccountBlocklist, MAccountBlocklistFormattable } from '@server/types/models'
  5. import { AttributesOnly } from '@shared/typescript-utils'
  6. import { AccountBlock } from '../../../shared/models'
  7. import { ActorModel } from '../actor/actor'
  8. import { ServerModel } from '../server/server'
  9. import { createSafeIn, getSort, searchAttribute } from '../shared'
  10. import { AccountModel } from './account'
  11. @Table({
  12. tableName: 'accountBlocklist',
  13. indexes: [
  14. {
  15. fields: [ 'accountId', 'targetAccountId' ],
  16. unique: true
  17. },
  18. {
  19. fields: [ 'targetAccountId' ]
  20. }
  21. ]
  22. })
  23. export class AccountBlocklistModel extends Model<Partial<AttributesOnly<AccountBlocklistModel>>> {
  24. @CreatedAt
  25. createdAt: Date
  26. @UpdatedAt
  27. updatedAt: Date
  28. @ForeignKey(() => AccountModel)
  29. @Column
  30. accountId: number
  31. @BelongsTo(() => AccountModel, {
  32. foreignKey: {
  33. name: 'accountId',
  34. allowNull: false
  35. },
  36. as: 'ByAccount',
  37. onDelete: 'CASCADE'
  38. })
  39. ByAccount: AccountModel
  40. @ForeignKey(() => AccountModel)
  41. @Column
  42. targetAccountId: number
  43. @BelongsTo(() => AccountModel, {
  44. foreignKey: {
  45. name: 'targetAccountId',
  46. allowNull: false
  47. },
  48. as: 'BlockedAccount',
  49. onDelete: 'CASCADE'
  50. })
  51. BlockedAccount: AccountModel
  52. static isAccountMutedByAccounts (accountIds: number[], targetAccountId: number) {
  53. const query = {
  54. attributes: [ 'accountId', 'id' ],
  55. where: {
  56. accountId: {
  57. [Op.in]: accountIds
  58. },
  59. targetAccountId
  60. },
  61. raw: true
  62. }
  63. return AccountBlocklistModel.unscoped()
  64. .findAll(query)
  65. .then(rows => {
  66. const result: { [accountId: number]: boolean } = {}
  67. for (const accountId of accountIds) {
  68. result[accountId] = !!rows.find(r => r.accountId === accountId)
  69. }
  70. return result
  71. })
  72. }
  73. static loadByAccountAndTarget (accountId: number, targetAccountId: number): Promise<MAccountBlocklist> {
  74. const query = {
  75. where: {
  76. accountId,
  77. targetAccountId
  78. }
  79. }
  80. return AccountBlocklistModel.findOne(query)
  81. }
  82. static listForApi (parameters: {
  83. start: number
  84. count: number
  85. sort: string
  86. search?: string
  87. accountId: number
  88. }) {
  89. const { start, count, sort, search, accountId } = parameters
  90. const getQuery = (forCount: boolean) => {
  91. const query: FindOptions = {
  92. offset: start,
  93. limit: count,
  94. order: getSort(sort),
  95. where: { accountId }
  96. }
  97. if (search) {
  98. Object.assign(query.where, {
  99. [Op.or]: [
  100. searchAttribute(search, '$BlockedAccount.name$'),
  101. searchAttribute(search, '$BlockedAccount.Actor.url$')
  102. ]
  103. })
  104. }
  105. if (forCount !== true) {
  106. query.include = [
  107. {
  108. model: AccountModel,
  109. required: true,
  110. as: 'ByAccount'
  111. },
  112. {
  113. model: AccountModel,
  114. required: true,
  115. as: 'BlockedAccount'
  116. }
  117. ]
  118. } else if (search) { // We need some joins when counting with search
  119. query.include = [
  120. {
  121. model: AccountModel.unscoped(),
  122. required: true,
  123. as: 'BlockedAccount',
  124. include: [
  125. {
  126. model: ActorModel.unscoped(),
  127. required: true
  128. }
  129. ]
  130. }
  131. ]
  132. }
  133. return query
  134. }
  135. return Promise.all([
  136. AccountBlocklistModel.count(getQuery(true)),
  137. AccountBlocklistModel.findAll(getQuery(false))
  138. ]).then(([ total, data ]) => ({ total, data }))
  139. }
  140. static listHandlesBlockedBy (accountIds: number[]): Promise<string[]> {
  141. const query = {
  142. attributes: [ 'id' ],
  143. where: {
  144. accountId: {
  145. [Op.in]: accountIds
  146. }
  147. },
  148. include: [
  149. {
  150. attributes: [ 'id' ],
  151. model: AccountModel.unscoped(),
  152. required: true,
  153. as: 'BlockedAccount',
  154. include: [
  155. {
  156. attributes: [ 'preferredUsername' ],
  157. model: ActorModel.unscoped(),
  158. required: true,
  159. include: [
  160. {
  161. attributes: [ 'host' ],
  162. model: ServerModel.unscoped(),
  163. required: true
  164. }
  165. ]
  166. }
  167. ]
  168. }
  169. ]
  170. }
  171. return AccountBlocklistModel.findAll(query)
  172. .then(entries => entries.map(e => `${e.BlockedAccount.Actor.preferredUsername}@${e.BlockedAccount.Actor.Server.host}`))
  173. }
  174. static getBlockStatus (byAccountIds: number[], handles: string[]): Promise<{ name: string, host: string, accountId: number }[]> {
  175. const sanitizedHandles = handlesToNameAndHost(handles)
  176. const localHandles = sanitizedHandles.filter(h => !h.host)
  177. .map(h => h.name)
  178. const remoteHandles = sanitizedHandles.filter(h => !!h.host)
  179. .map(h => ([ h.name, h.host ]))
  180. const handlesWhere: string[] = []
  181. if (localHandles.length !== 0) {
  182. handlesWhere.push(`("actor"."preferredUsername" IN (:localHandles) AND "server"."id" IS NULL)`)
  183. }
  184. if (remoteHandles.length !== 0) {
  185. handlesWhere.push(`(("actor"."preferredUsername", "server"."host") IN (:remoteHandles))`)
  186. }
  187. const rawQuery = `SELECT "accountBlocklist"."accountId", "actor"."preferredUsername" AS "name", "server"."host" ` +
  188. `FROM "accountBlocklist" ` +
  189. `INNER JOIN "account" ON "account"."id" = "accountBlocklist"."targetAccountId" ` +
  190. `INNER JOIN "actor" ON "actor"."id" = "account"."actorId" ` +
  191. `LEFT JOIN "server" ON "server"."id" = "actor"."serverId" ` +
  192. `WHERE "accountBlocklist"."accountId" IN (${createSafeIn(AccountBlocklistModel.sequelize, byAccountIds)}) ` +
  193. `AND (${handlesWhere.join(' OR ')})`
  194. return AccountBlocklistModel.sequelize.query(rawQuery, {
  195. type: QueryTypes.SELECT as QueryTypes.SELECT,
  196. replacements: { byAccountIds, localHandles, remoteHandles }
  197. })
  198. }
  199. toFormattedJSON (this: MAccountBlocklistFormattable): AccountBlock {
  200. return {
  201. byAccount: this.ByAccount.toFormattedJSON(),
  202. blockedAccount: this.BlockedAccount.toFormattedJSON(),
  203. createdAt: this.createdAt
  204. }
  205. }
  206. }