PeerTube/server/models/account/account.ts

464 lines
11 KiB
TypeScript
Raw Normal View History

2017-12-04 03:34:40 -06:00
import { join } from 'path'
2017-11-09 10:51:58 -06:00
import * as Sequelize from 'sequelize'
2017-12-04 03:34:40 -06:00
import { Avatar } from '../../../shared/models/avatars/avatar.model'
2017-11-09 10:51:58 -06:00
import {
2017-11-17 08:52:26 -06:00
activityPubContextify,
2017-11-09 10:51:58 -06:00
isAccountFollowersCountValid,
isAccountFollowingCountValid,
2017-11-17 08:52:26 -06:00
isAccountPrivateKeyValid,
isAccountPublicKeyValid,
isUserUsernameValid
2017-11-09 10:51:58 -06:00
} from '../../helpers'
2017-11-27 10:30:46 -06:00
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
2017-12-04 03:34:40 -06:00
import { AVATARS_DIR } from '../../initializers'
import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers/constants'
2017-11-20 02:43:39 -06:00
import { sendDeleteAccount } from '../../lib/activitypub/send/send-delete'
2017-11-17 08:52:26 -06:00
import { addMethodsToModel } from '../utils'
import { AccountAttributes, AccountInstance, AccountMethods } from './account-interface'
2017-11-09 10:51:58 -06:00
let Account: Sequelize.Model<AccountInstance, AccountAttributes>
let load: AccountMethods.Load
2017-11-13 10:39:41 -06:00
let loadApplication: AccountMethods.LoadApplication
2017-11-09 10:51:58 -06:00
let loadByUUID: AccountMethods.LoadByUUID
let loadByUrl: AccountMethods.LoadByUrl
2017-11-14 10:31:26 -06:00
let loadLocalByName: AccountMethods.LoadLocalByName
let loadByNameAndHost: AccountMethods.LoadByNameAndHost
let listByFollowersUrls: AccountMethods.ListByFollowersUrls
2017-11-09 10:51:58 -06:00
let isOwned: AccountMethods.IsOwned
let toActivityPubObject: AccountMethods.ToActivityPubObject
2017-11-13 10:39:41 -06:00
let toFormattedJSON: AccountMethods.ToFormattedJSON
2017-11-09 10:51:58 -06:00
let getFollowerSharedInboxUrls: AccountMethods.GetFollowerSharedInboxUrls
let getFollowingUrl: AccountMethods.GetFollowingUrl
let getFollowersUrl: AccountMethods.GetFollowersUrl
let getPublicKeyUrl: AccountMethods.GetPublicKeyUrl
export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
Account = sequelize.define<AccountInstance, AccountAttributes>('Account',
{
uuid: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
allowNull: false,
validate: {
isUUID: 4
}
},
name: {
type: DataTypes.STRING,
allowNull: false,
validate: {
2017-11-14 03:57:56 -06:00
nameValid: value => {
2017-11-09 10:51:58 -06:00
const res = isUserUsernameValid(value)
2017-11-14 03:57:56 -06:00
if (res === false) throw new Error('Name is not valid.')
2017-11-09 10:51:58 -06:00
}
}
},
url: {
2017-11-14 03:57:56 -06:00
type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
2017-11-09 10:51:58 -06:00
allowNull: false,
validate: {
urlValid: value => {
2017-11-27 10:30:46 -06:00
const res = isActivityPubUrlValid(value)
2017-11-09 10:51:58 -06:00
if (res === false) throw new Error('URL is not valid.')
}
}
},
publicKey: {
2017-11-14 03:57:56 -06:00
type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PUBLIC_KEY.max),
2017-11-16 11:40:50 -06:00
allowNull: true,
2017-11-09 10:51:58 -06:00
validate: {
publicKeyValid: value => {
const res = isAccountPublicKeyValid(value)
if (res === false) throw new Error('Public key is not valid.')
}
}
},
privateKey: {
2017-11-14 03:57:56 -06:00
type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PRIVATE_KEY.max),
2017-11-14 10:31:26 -06:00
allowNull: true,
2017-11-09 10:51:58 -06:00
validate: {
privateKeyValid: value => {
const res = isAccountPrivateKeyValid(value)
if (res === false) throw new Error('Private key is not valid.')
}
}
},
followersCount: {
type: DataTypes.INTEGER,
allowNull: false,
validate: {
followersCountValid: value => {
const res = isAccountFollowersCountValid(value)
if (res === false) throw new Error('Followers count is not valid.')
}
}
},
followingCount: {
type: DataTypes.INTEGER,
allowNull: false,
validate: {
2017-11-14 03:57:56 -06:00
followingCountValid: value => {
2017-11-09 10:51:58 -06:00
const res = isAccountFollowingCountValid(value)
if (res === false) throw new Error('Following count is not valid.')
}
}
},
inboxUrl: {
2017-11-14 03:57:56 -06:00
type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
2017-11-09 10:51:58 -06:00
allowNull: false,
validate: {
inboxUrlValid: value => {
2017-11-27 10:30:46 -06:00
const res = isActivityPubUrlValid(value)
2017-11-09 10:51:58 -06:00
if (res === false) throw new Error('Inbox URL is not valid.')
}
}
},
outboxUrl: {
2017-11-14 03:57:56 -06:00
type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
2017-11-09 10:51:58 -06:00
allowNull: false,
validate: {
outboxUrlValid: value => {
2017-11-27 10:30:46 -06:00
const res = isActivityPubUrlValid(value)
2017-11-09 10:51:58 -06:00
if (res === false) throw new Error('Outbox URL is not valid.')
}
}
},
sharedInboxUrl: {
2017-11-14 03:57:56 -06:00
type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
2017-11-09 10:51:58 -06:00
allowNull: false,
validate: {
sharedInboxUrlValid: value => {
2017-11-27 10:30:46 -06:00
const res = isActivityPubUrlValid(value)
2017-11-09 10:51:58 -06:00
if (res === false) throw new Error('Shared inbox URL is not valid.')
}
}
},
followersUrl: {
2017-11-14 03:57:56 -06:00
type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
2017-11-09 10:51:58 -06:00
allowNull: false,
validate: {
followersUrlValid: value => {
2017-11-27 10:30:46 -06:00
const res = isActivityPubUrlValid(value)
2017-11-09 10:51:58 -06:00
if (res === false) throw new Error('Followers URL is not valid.')
}
}
},
followingUrl: {
2017-11-14 03:57:56 -06:00
type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
2017-11-09 10:51:58 -06:00
allowNull: false,
validate: {
followingUrlValid: value => {
2017-11-27 10:30:46 -06:00
const res = isActivityPubUrlValid(value)
2017-11-09 10:51:58 -06:00
if (res === false) throw new Error('Following URL is not valid.')
}
}
}
},
{
indexes: [
{
fields: [ 'name' ]
},
{
2017-11-15 04:00:25 -06:00
fields: [ 'serverId' ]
2017-11-09 10:51:58 -06:00
},
{
fields: [ 'userId' ],
unique: true
},
{
fields: [ 'applicationId' ],
unique: true
},
{
2017-11-15 04:00:25 -06:00
fields: [ 'name', 'serverId', 'applicationId' ],
2017-11-09 10:51:58 -06:00
unique: true
}
],
hooks: { afterDestroy }
}
)
const classMethods = [
associate,
2017-11-13 10:39:41 -06:00
loadApplication,
2017-11-09 10:51:58 -06:00
load,
loadByUUID,
2017-11-13 11:48:28 -06:00
loadByUrl,
2017-11-14 10:31:26 -06:00
loadLocalByName,
loadByNameAndHost,
listByFollowersUrls
2017-11-09 10:51:58 -06:00
]
const instanceMethods = [
isOwned,
toActivityPubObject,
2017-11-13 10:39:41 -06:00
toFormattedJSON,
2017-11-09 10:51:58 -06:00
getFollowerSharedInboxUrls,
getFollowingUrl,
getFollowersUrl,
getPublicKeyUrl
]
addMethodsToModel(Account, classMethods, instanceMethods)
return Account
}
// ---------------------------------------------------------------------------
function associate (models) {
2017-11-15 04:00:25 -06:00
Account.belongsTo(models.Server, {
2017-11-09 10:51:58 -06:00
foreignKey: {
2017-11-15 04:00:25 -06:00
name: 'serverId',
2017-11-09 10:51:58 -06:00
allowNull: true
},
onDelete: 'cascade'
})
Account.belongsTo(models.User, {
foreignKey: {
name: 'userId',
allowNull: true
},
onDelete: 'cascade'
})
Account.belongsTo(models.Application, {
foreignKey: {
2017-11-14 03:57:56 -06:00
name: 'applicationId',
2017-11-09 10:51:58 -06:00
allowNull: true
},
onDelete: 'cascade'
})
Account.hasMany(models.VideoChannel, {
foreignKey: {
name: 'accountId',
allowNull: false
},
onDelete: 'cascade',
hooks: true
})
2017-11-14 03:57:56 -06:00
Account.hasMany(models.AccountFollow, {
2017-11-09 10:51:58 -06:00
foreignKey: {
name: 'accountId',
allowNull: false
},
onDelete: 'cascade'
})
2017-11-14 03:57:56 -06:00
Account.hasMany(models.AccountFollow, {
2017-11-09 10:51:58 -06:00
foreignKey: {
name: 'targetAccountId',
allowNull: false
},
2017-11-15 09:28:35 -06:00
as: 'followers',
2017-11-09 10:51:58 -06:00
onDelete: 'cascade'
})
2017-12-04 03:34:40 -06:00
Account.hasOne(models.Avatar, {
foreignKey: {
name: 'avatarId',
allowNull: true
},
onDelete: 'cascade'
})
2017-11-09 10:51:58 -06:00
}
function afterDestroy (account: AccountInstance) {
if (account.isOwned()) {
2017-11-13 10:39:41 -06:00
return sendDeleteAccount(account, undefined)
2017-11-09 10:51:58 -06:00
}
return undefined
}
2017-11-13 10:39:41 -06:00
toFormattedJSON = function (this: AccountInstance) {
2017-11-15 04:00:25 -06:00
let host = CONFIG.WEBSERVER.HOST
let score: number
2017-12-04 03:34:40 -06:00
let avatar: Avatar = null
if (this.Avatar) {
avatar = {
path: join(AVATARS_DIR.ACCOUNT, this.Avatar.filename),
createdAt: this.Avatar.createdAt,
updatedAt: this.Avatar.updatedAt
}
}
2017-11-15 04:00:25 -06:00
if (this.Server) {
host = this.Server.host
score = this.Server.score as number
}
2017-11-13 10:39:41 -06:00
const json = {
id: this.id,
2017-12-04 03:34:40 -06:00
uuid: this.uuid,
host,
2017-11-15 04:00:25 -06:00
score,
name: this.name,
2017-12-04 03:34:40 -06:00
followingCount: this.followingCount,
followersCount: this.followersCount,
2017-11-15 04:00:25 -06:00
createdAt: this.createdAt,
2017-12-04 03:34:40 -06:00
updatedAt: this.updatedAt,
avatar
2017-11-13 10:39:41 -06:00
}
return json
}
2017-11-09 10:51:58 -06:00
toActivityPubObject = function (this: AccountInstance) {
2017-11-15 04:00:25 -06:00
const type = this.serverId ? 'Application' as 'Application' : 'Person' as 'Person'
2017-11-09 10:51:58 -06:00
const json = {
type,
id: this.url,
following: this.getFollowingUrl(),
followers: this.getFollowersUrl(),
inbox: this.inboxUrl,
outbox: this.outboxUrl,
preferredUsername: this.name,
url: this.url,
name: this.name,
endpoints: {
sharedInbox: this.sharedInboxUrl
},
uuid: this.uuid,
publicKey: {
id: this.getPublicKeyUrl(),
owner: this.url,
publicKeyPem: this.publicKey
}
}
return activityPubContextify(json)
}
isOwned = function (this: AccountInstance) {
2017-11-15 04:00:25 -06:00
return this.serverId === null
2017-11-09 10:51:58 -06:00
}
getFollowerSharedInboxUrls = function (this: AccountInstance, t: Sequelize.Transaction) {
2017-11-09 10:51:58 -06:00
const query: Sequelize.FindOptions<AccountAttributes> = {
attributes: [ 'sharedInboxUrl' ],
include: [
{
2017-11-14 03:57:56 -06:00
model: Account['sequelize'].models.AccountFollow,
2017-11-14 10:31:26 -06:00
required: true,
as: 'followers',
2017-11-09 10:51:58 -06:00
where: {
targetAccountId: this.id
}
}
],
transaction: t
2017-11-09 10:51:58 -06:00
}
return Account.findAll(query)
.then(accounts => accounts.map(a => a.sharedInboxUrl))
}
getFollowingUrl = function (this: AccountInstance) {
return this.url + '/following'
2017-11-09 10:51:58 -06:00
}
getFollowersUrl = function (this: AccountInstance) {
return this.url + '/followers'
}
getPublicKeyUrl = function (this: AccountInstance) {
return this.url + '#main-key'
}
// ------------------------------ STATICS ------------------------------
2017-11-13 10:39:41 -06:00
loadApplication = function () {
return Account.findOne({
include: [
{
2017-11-14 10:31:26 -06:00
model: Account['sequelize'].models.Application,
2017-11-13 10:39:41 -06:00
required: true
}
]
})
2017-11-09 10:51:58 -06:00
}
load = function (id: number) {
return Account.findById(id)
}
loadByUUID = function (uuid: string) {
const query: Sequelize.FindOptions<AccountAttributes> = {
where: {
uuid
}
}
return Account.findOne(query)
}
2017-11-14 10:31:26 -06:00
loadLocalByName = function (name: string) {
2017-11-09 10:51:58 -06:00
const query: Sequelize.FindOptions<AccountAttributes> = {
where: {
name,
2017-11-14 10:31:26 -06:00
[Sequelize.Op.or]: [
{
userId: {
[Sequelize.Op.ne]: null
}
},
{
applicationId: {
[Sequelize.Op.ne]: null
}
}
]
}
}
return Account.findOne(query)
}
loadByNameAndHost = function (name: string, host: string) {
const query: Sequelize.FindOptions<AccountAttributes> = {
where: {
name
2017-11-13 10:39:41 -06:00
},
include: [
{
2017-11-15 04:00:25 -06:00
model: Account['sequelize'].models.Server,
2017-11-14 10:31:26 -06:00
required: true,
2017-11-13 10:39:41 -06:00
where: {
host
}
}
]
2017-11-09 10:51:58 -06:00
}
return Account.findOne(query)
}
2017-11-13 11:48:28 -06:00
loadByUrl = function (url: string, transaction?: Sequelize.Transaction) {
2017-11-09 10:51:58 -06:00
const query: Sequelize.FindOptions<AccountAttributes> = {
where: {
url
2017-11-13 11:48:28 -06:00
},
transaction
2017-11-09 10:51:58 -06:00
}
return Account.findOne(query)
}
listByFollowersUrls = function (followersUrls: string[], transaction?: Sequelize.Transaction) {
const query: Sequelize.FindOptions<AccountAttributes> = {
where: {
followersUrl: {
[Sequelize.Op.in]: followersUrls
}
},
transaction
}
return Account.findAll(query)
}