Add bio fields (#6645)
* Add bio fields - Fix #3211 - Fix #232 - Fix #121 * Display bio fields in web UI * Fix output of links and missing fields * Federate bio fields over ActivityPub as PropertyValue * Improve how the fields are stored, add to Edit profile form * Add rel=me to links in fields Fix #121gh/stable
parent
85ab30abf7
commit
78ed4ab75f
|
@ -11,7 +11,9 @@ class Settings::ProfilesController < ApplicationController
|
||||||
obfuscate_filename [:account, :avatar]
|
obfuscate_filename [:account, :avatar]
|
||||||
obfuscate_filename [:account, :header]
|
obfuscate_filename [:account, :header]
|
||||||
|
|
||||||
def show; end
|
def show
|
||||||
|
@account.build_fields
|
||||||
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
if UpdateAccountService.new.call(@account, account_params)
|
if UpdateAccountService.new.call(@account, account_params)
|
||||||
|
@ -25,7 +27,7 @@ class Settings::ProfilesController < ApplicationController
|
||||||
private
|
private
|
||||||
|
|
||||||
def account_params
|
def account_params
|
||||||
params.require(:account).permit(:display_name, :note, :avatar, :header, :locked)
|
params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, fields_attributes: [:name, :value])
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_account
|
def set_account
|
||||||
|
|
|
@ -10,6 +10,14 @@ export function normalizeAccount(account) {
|
||||||
account.display_name_html = emojify(escapeTextContentForBrowser(displayName));
|
account.display_name_html = emojify(escapeTextContentForBrowser(displayName));
|
||||||
account.note_emojified = emojify(account.note);
|
account.note_emojified = emojify(account.note);
|
||||||
|
|
||||||
|
if (account.fields) {
|
||||||
|
account.fields = account.fields.map(pair => ({
|
||||||
|
...pair,
|
||||||
|
name_emojified: emojify(escapeTextContentForBrowser(pair.name)),
|
||||||
|
value_emojified: emojify(pair.value),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
if (account.moved) {
|
if (account.moved) {
|
||||||
account.moved = account.moved.id;
|
account.moved = account.moved.id;
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,6 +130,7 @@ export default class Header extends ImmutablePureComponent {
|
||||||
|
|
||||||
const content = { __html: account.get('note_emojified') };
|
const content = { __html: account.get('note_emojified') };
|
||||||
const displayNameHtml = { __html: account.get('display_name_html') };
|
const displayNameHtml = { __html: account.get('display_name_html') };
|
||||||
|
const fields = account.get('fields');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames('account__header', { inactive: !!account.get('moved') })} style={{ backgroundImage: `url(${account.get('header')})` }}>
|
<div className={classNames('account__header', { inactive: !!account.get('moved') })} style={{ backgroundImage: `url(${account.get('header')})` }}>
|
||||||
|
@ -140,6 +141,19 @@ export default class Header extends ImmutablePureComponent {
|
||||||
<span className='account__header__username'>@{account.get('acct')} {lockedIcon}</span>
|
<span className='account__header__username'>@{account.get('acct')} {lockedIcon}</span>
|
||||||
<div className='account__header__content' dangerouslySetInnerHTML={content} />
|
<div className='account__header__content' dangerouslySetInnerHTML={content} />
|
||||||
|
|
||||||
|
{fields.size > 0 && (
|
||||||
|
<table className='account__header__fields'>
|
||||||
|
<tbody>
|
||||||
|
{fields.map((pair, i) => (
|
||||||
|
<tr key={i}>
|
||||||
|
<th dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} />
|
||||||
|
<td dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} />
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)}
|
||||||
|
|
||||||
{info}
|
{info}
|
||||||
{mutingInfo}
|
{mutingInfo}
|
||||||
{actionBtn}
|
{actionBtn}
|
||||||
|
|
|
@ -563,3 +563,57 @@
|
||||||
border-color: rgba(lighten($error-red, 12%), 0.5);
|
border-color: rgba(lighten($error-red, 12%), 0.5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.account__header__fields {
|
||||||
|
border-collapse: collapse;
|
||||||
|
padding: 0;
|
||||||
|
margin: 15px -15px -15px;
|
||||||
|
border: 0 none;
|
||||||
|
border-top: 1px solid lighten($ui-base-color, 4%);
|
||||||
|
border-bottom: 1px solid lighten($ui-base-color, 4%);
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
padding: 15px;
|
||||||
|
padding-left: 15px;
|
||||||
|
border: 0 none;
|
||||||
|
border-bottom: 1px solid lighten($ui-base-color, 4%);
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
padding-left: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
width: 94px;
|
||||||
|
color: $ui-secondary-color;
|
||||||
|
background: rgba(darken($ui-base-color, 8%), 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
color: $ui-primary-color;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $ui-highlight-color;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
&:last-child {
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5176,3 +5176,40 @@ noscript {
|
||||||
background: lighten($ui-highlight-color, 7%);
|
background: lighten($ui-highlight-color, 7%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.account__header .account__header__fields {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 20px -10px -20px;
|
||||||
|
border-bottom: 0;
|
||||||
|
|
||||||
|
tr {
|
||||||
|
border-top: 1px solid lighten($ui-base-color, 8%);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
padding: 14px 20px;
|
||||||
|
vertical-align: middle;
|
||||||
|
max-height: 40px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
color: $ui-primary-color;
|
||||||
|
background: darken($ui-base-color, 4%);
|
||||||
|
max-width: 120px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
flex: auto;
|
||||||
|
color: $primary-text-color;
|
||||||
|
background: $ui-base-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,18 @@ code {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
margin: 0 -5px;
|
||||||
|
|
||||||
|
.input {
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
width: 50%;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
span.hint {
|
span.hint {
|
||||||
display: block;
|
display: block;
|
||||||
color: $ui-primary-color;
|
color: $ui-primary-color;
|
||||||
|
|
|
@ -19,6 +19,9 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
|
||||||
'Emoji' => 'toot:Emoji',
|
'Emoji' => 'toot:Emoji',
|
||||||
'focalPoint' => { '@container' => '@list', '@id' => 'toot:focalPoint' },
|
'focalPoint' => { '@container' => '@list', '@id' => 'toot:focalPoint' },
|
||||||
'featured' => 'toot:featured',
|
'featured' => 'toot:featured',
|
||||||
|
'schema' => 'http://schema.org#',
|
||||||
|
'PropertyValue' => 'schema:PropertyValue',
|
||||||
|
'value' => 'schema:value',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
|
@ -71,6 +71,11 @@ class Formatter
|
||||||
html.html_safe # rubocop:disable Rails/OutputSafety
|
html.html_safe # rubocop:disable Rails/OutputSafety
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def format_field(account, str)
|
||||||
|
return reformat(str).html_safe unless account.local? # rubocop:disable Rails/OutputSafety
|
||||||
|
encode_and_link_urls(str, me: true).html_safe # rubocop:disable Rails/OutputSafety
|
||||||
|
end
|
||||||
|
|
||||||
def linkify(text)
|
def linkify(text)
|
||||||
html = encode_and_link_urls(text)
|
html = encode_and_link_urls(text)
|
||||||
html = simple_format(html, {}, sanitize: false)
|
html = simple_format(html, {}, sanitize: false)
|
||||||
|
@ -85,12 +90,17 @@ class Formatter
|
||||||
HTMLEntities.new.encode(html)
|
HTMLEntities.new.encode(html)
|
||||||
end
|
end
|
||||||
|
|
||||||
def encode_and_link_urls(html, accounts = nil)
|
def encode_and_link_urls(html, accounts = nil, options = {})
|
||||||
entities = Extractor.extract_entities_with_indices(html, extract_url_without_protocol: false)
|
entities = Extractor.extract_entities_with_indices(html, extract_url_without_protocol: false)
|
||||||
|
|
||||||
|
if accounts.is_a?(Hash)
|
||||||
|
options = accounts
|
||||||
|
accounts = nil
|
||||||
|
end
|
||||||
|
|
||||||
rewrite(html.dup, entities) do |entity|
|
rewrite(html.dup, entities) do |entity|
|
||||||
if entity[:url]
|
if entity[:url]
|
||||||
link_to_url(entity)
|
link_to_url(entity, options)
|
||||||
elsif entity[:hashtag]
|
elsif entity[:hashtag]
|
||||||
link_to_hashtag(entity)
|
link_to_hashtag(entity)
|
||||||
elsif entity[:screen_name]
|
elsif entity[:screen_name]
|
||||||
|
@ -177,10 +187,12 @@ class Formatter
|
||||||
result.flatten.join
|
result.flatten.join
|
||||||
end
|
end
|
||||||
|
|
||||||
def link_to_url(entity)
|
def link_to_url(entity, options = {})
|
||||||
url = Addressable::URI.parse(entity[:url])
|
url = Addressable::URI.parse(entity[:url])
|
||||||
html_attrs = { target: '_blank', rel: 'nofollow noopener' }
|
html_attrs = { target: '_blank', rel: 'nofollow noopener' }
|
||||||
|
|
||||||
|
html_attrs[:rel] = "me #{html_attrs[:rel]}" if options[:me]
|
||||||
|
|
||||||
Twitter::Autolink.send(:link_to_text, entity, link_html(entity[:url]), url, html_attrs)
|
Twitter::Autolink.send(:link_to_text, entity, link_html(entity[:url]), url, html_attrs)
|
||||||
rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
|
rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
|
||||||
encode(entity[:url])
|
encode(entity[:url])
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
# memorial :boolean default(FALSE), not null
|
# memorial :boolean default(FALSE), not null
|
||||||
# moved_to_account_id :integer
|
# moved_to_account_id :integer
|
||||||
# featured_collection_url :string
|
# featured_collection_url :string
|
||||||
|
# fields :jsonb
|
||||||
#
|
#
|
||||||
|
|
||||||
class Account < ApplicationRecord
|
class Account < ApplicationRecord
|
||||||
|
@ -189,6 +190,30 @@ class Account < ApplicationRecord
|
||||||
@keypair ||= OpenSSL::PKey::RSA.new(private_key || public_key)
|
@keypair ||= OpenSSL::PKey::RSA.new(private_key || public_key)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fields
|
||||||
|
(self[:fields] || []).map { |f| Field.new(self, f) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def fields_attributes=(attributes)
|
||||||
|
fields = []
|
||||||
|
|
||||||
|
attributes.each_value do |attr|
|
||||||
|
next if attr[:name].blank?
|
||||||
|
fields << attr
|
||||||
|
end
|
||||||
|
|
||||||
|
self[:fields] = fields
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_fields
|
||||||
|
return if fields.size >= 4
|
||||||
|
|
||||||
|
raw_fields = self[:fields] || []
|
||||||
|
add_fields = 4 - raw_fields.size
|
||||||
|
add_fields.times { raw_fields << { name: '', value: '' } }
|
||||||
|
self.fields = raw_fields
|
||||||
|
end
|
||||||
|
|
||||||
def magic_key
|
def magic_key
|
||||||
modulus, exponent = [keypair.public_key.n, keypair.public_key.e].map do |component|
|
modulus, exponent = [keypair.public_key.n, keypair.public_key.e].map do |component|
|
||||||
result = []
|
result = []
|
||||||
|
@ -238,6 +263,17 @@ class Account < ApplicationRecord
|
||||||
shared_inbox_url.presence || inbox_url
|
shared_inbox_url.presence || inbox_url
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class Field < ActiveModelSerializers::Model
|
||||||
|
attributes :name, :value, :account, :errors
|
||||||
|
|
||||||
|
def initialize(account, attr)
|
||||||
|
@account = account
|
||||||
|
@name = attr['name']
|
||||||
|
@value = attr['value']
|
||||||
|
@errors = {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def readonly_attributes
|
def readonly_attributes
|
||||||
super - %w(statuses_count following_count followers_count)
|
super - %w(statuses_count following_count followers_count)
|
||||||
|
|
|
@ -11,6 +11,7 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer
|
||||||
has_one :public_key, serializer: ActivityPub::PublicKeySerializer
|
has_one :public_key, serializer: ActivityPub::PublicKeySerializer
|
||||||
|
|
||||||
has_many :virtual_tags, key: :tag
|
has_many :virtual_tags, key: :tag
|
||||||
|
has_many :virtual_attachments, key: :attachment
|
||||||
|
|
||||||
attribute :moved_to, if: :moved?
|
attribute :moved_to, if: :moved?
|
||||||
|
|
||||||
|
@ -107,10 +108,26 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer
|
||||||
object.emojis
|
object.emojis
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def virtual_attachments
|
||||||
|
object.fields
|
||||||
|
end
|
||||||
|
|
||||||
def moved_to
|
def moved_to
|
||||||
ActivityPub::TagManager.instance.uri_for(object.moved_to_account)
|
ActivityPub::TagManager.instance.uri_for(object.moved_to_account)
|
||||||
end
|
end
|
||||||
|
|
||||||
class CustomEmojiSerializer < ActivityPub::EmojiSerializer
|
class CustomEmojiSerializer < ActivityPub::EmojiSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class Account::FieldSerializer < ActiveModel::Serializer
|
||||||
|
attributes :type, :name, :value
|
||||||
|
|
||||||
|
def type
|
||||||
|
'PropertyValue'
|
||||||
|
end
|
||||||
|
|
||||||
|
def value
|
||||||
|
Formatter.instance.format_field(object.account, object.value)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,6 +9,16 @@ class REST::AccountSerializer < ActiveModel::Serializer
|
||||||
|
|
||||||
has_one :moved_to_account, key: :moved, serializer: REST::AccountSerializer, if: :moved_and_not_nested?
|
has_one :moved_to_account, key: :moved, serializer: REST::AccountSerializer, if: :moved_and_not_nested?
|
||||||
|
|
||||||
|
class FieldSerializer < ActiveModel::Serializer
|
||||||
|
attributes :name, :value
|
||||||
|
|
||||||
|
def value
|
||||||
|
Formatter.instance.format_field(object.account, object.value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
has_many :fields
|
||||||
|
|
||||||
def id
|
def id
|
||||||
object.id.to_s
|
object.id.to_s
|
||||||
end
|
end
|
||||||
|
|
|
@ -70,6 +70,7 @@ class ActivityPub::ProcessAccountService < BaseService
|
||||||
@account.display_name = @json['name'] || ''
|
@account.display_name = @json['name'] || ''
|
||||||
@account.note = @json['summary'] || ''
|
@account.note = @json['summary'] || ''
|
||||||
@account.locked = @json['manuallyApprovesFollowers'] || false
|
@account.locked = @json['manuallyApprovesFollowers'] || false
|
||||||
|
@account.fields = property_values || {}
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_fetchable_attributes!
|
def set_fetchable_attributes!
|
||||||
|
@ -126,6 +127,11 @@ class ActivityPub::ProcessAccountService < BaseService
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def property_values
|
||||||
|
return unless @json['attachment'].is_a?(Array)
|
||||||
|
@json['attachment'].select { |attachment| attachment['type'] == 'PropertyValue' }.map { |attachment| attachment.slice('name', 'value') }
|
||||||
|
end
|
||||||
|
|
||||||
def mismatching_origin?(url)
|
def mismatching_origin?(url)
|
||||||
needle = Addressable::URI.parse(url).host
|
needle = Addressable::URI.parse(url).host
|
||||||
haystack = Addressable::URI.parse(@uri).host
|
haystack = Addressable::URI.parse(@uri).host
|
||||||
|
|
|
@ -23,6 +23,14 @@
|
||||||
.bio
|
.bio
|
||||||
.account__header__content.p-note.emojify= Formatter.instance.simplified_format(account, custom_emojify: true)
|
.account__header__content.p-note.emojify= Formatter.instance.simplified_format(account, custom_emojify: true)
|
||||||
|
|
||||||
|
- unless account.fields.empty?
|
||||||
|
%table.account__header__fields
|
||||||
|
%tbody
|
||||||
|
- account.fields.each do |field|
|
||||||
|
%tr
|
||||||
|
%th.emojify= field.name
|
||||||
|
%td.emojify= Formatter.instance.format_field(account, field.value)
|
||||||
|
|
||||||
.details-counters
|
.details-counters
|
||||||
.counter{ class: active_nav_class(short_account_url(account)) }
|
.counter{ class: active_nav_class(short_account_url(account)) }
|
||||||
= link_to short_account_url(account), class: 'u-url u-uid' do
|
= link_to short_account_url(account), class: 'u-url u-uid' do
|
||||||
|
|
|
@ -19,6 +19,16 @@
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :locked, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.locked')
|
= f.input :locked, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.locked')
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
.input.with_block_label
|
||||||
|
%label= t('simple_form.labels.defaults.fields')
|
||||||
|
%span.hint= t('simple_form.hints.defaults.fields')
|
||||||
|
|
||||||
|
= f.simple_fields_for :fields do |fields_f|
|
||||||
|
.row
|
||||||
|
= fields_f.input :name, placeholder: t('simple_form.labels.account.fields.name')
|
||||||
|
= fields_f.input :value, placeholder: t('simple_form.labels.account.fields.value')
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= f.button :button, t('generic.save_changes'), type: :submit
|
= f.button :button, t('generic.save_changes'), type: :submit
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ en:
|
||||||
display_name:
|
display_name:
|
||||||
one: <span class="name-counter">1</span> character left
|
one: <span class="name-counter">1</span> character left
|
||||||
other: <span class="name-counter">%{count}</span> characters left
|
other: <span class="name-counter">%{count}</span> characters left
|
||||||
|
fields: You can have up to 4 items displayed as a table on your profile
|
||||||
header: PNG, GIF or JPG. At most 2MB. Will be downscaled to 700x335px
|
header: PNG, GIF or JPG. At most 2MB. Will be downscaled to 700x335px
|
||||||
locked: Requires you to manually approve followers
|
locked: Requires you to manually approve followers
|
||||||
note:
|
note:
|
||||||
|
@ -22,6 +23,10 @@ en:
|
||||||
user:
|
user:
|
||||||
filtered_languages: Checked languages will be filtered from public timelines for you
|
filtered_languages: Checked languages will be filtered from public timelines for you
|
||||||
labels:
|
labels:
|
||||||
|
account:
|
||||||
|
fields:
|
||||||
|
name: Label
|
||||||
|
value: Content
|
||||||
defaults:
|
defaults:
|
||||||
avatar: Avatar
|
avatar: Avatar
|
||||||
confirm_new_password: Confirm new password
|
confirm_new_password: Confirm new password
|
||||||
|
@ -31,6 +36,7 @@ en:
|
||||||
display_name: Display name
|
display_name: Display name
|
||||||
email: E-mail address
|
email: E-mail address
|
||||||
expires_in: Expire after
|
expires_in: Expire after
|
||||||
|
fields: Profile metadata
|
||||||
filtered_languages: Filtered languages
|
filtered_languages: Filtered languages
|
||||||
header: Header
|
header: Header
|
||||||
locale: Language
|
locale: Language
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddFieldsToAccounts < ActiveRecord::Migration[5.1]
|
||||||
|
def change
|
||||||
|
add_column :accounts, :fields, :jsonb
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2018_04_02_040909) do
|
ActiveRecord::Schema.define(version: 2018_04_10_204633) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "pg_stat_statements"
|
enable_extension "pg_stat_statements"
|
||||||
|
@ -75,6 +75,7 @@ ActiveRecord::Schema.define(version: 2018_04_02_040909) do
|
||||||
t.boolean "memorial", default: false, null: false
|
t.boolean "memorial", default: false, null: false
|
||||||
t.bigint "moved_to_account_id"
|
t.bigint "moved_to_account_id"
|
||||||
t.string "featured_collection_url"
|
t.string "featured_collection_url"
|
||||||
|
t.jsonb "fields"
|
||||||
t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin
|
t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin
|
||||||
t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower"
|
t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower"
|
||||||
t.index ["uri"], name: "index_accounts_on_uri"
|
t.index ["uri"], name: "index_accounts_on_uri"
|
||||||
|
|
|
@ -1,5 +1,31 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe ActivityPub::ProcessAccountService do
|
RSpec.describe ActivityPub::ProcessAccountService do
|
||||||
pending
|
subject { described_class.new }
|
||||||
|
|
||||||
|
context 'property values' do
|
||||||
|
let(:payload) do
|
||||||
|
{
|
||||||
|
id: 'https://foo',
|
||||||
|
type: 'Actor',
|
||||||
|
inbox: 'https://foo/inbox',
|
||||||
|
attachment: [
|
||||||
|
{ type: 'PropertyValue', name: 'Pronouns', value: 'They/them' },
|
||||||
|
{ type: 'PropertyValue', name: 'Occupation', value: 'Unit test' },
|
||||||
|
],
|
||||||
|
}.with_indifferent_access
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'parses out of attachment' do
|
||||||
|
account = subject.call('alice', 'example.com', payload)
|
||||||
|
expect(account.fields).to be_a Array
|
||||||
|
expect(account.fields.size).to eq 2
|
||||||
|
expect(account.fields[0]).to be_a Account::Field
|
||||||
|
expect(account.fields[0].name).to eq 'Pronouns'
|
||||||
|
expect(account.fields[0].value).to eq 'They/them'
|
||||||
|
expect(account.fields[1]).to be_a Account::Field
|
||||||
|
expect(account.fields[1].name).to eq 'Occupation'
|
||||||
|
expect(account.fields[1].value).to eq 'Unit test'
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Reference in New Issue