Commit 27a4c1bf authored by Maxwell Salzberg's avatar Maxwell Salzberg

introduce the idea of Federated::Base. this is mostly just renaming and...

introduce the idea of Federated::Base. this is mostly just renaming and collasping of different federation modules, but also starting a direct hiearchy of these federation classes to make everything easier to refactor
parent 49b0a447
......@@ -3,8 +3,7 @@
# the COPYRIGHT file.
class AccountDeletion < ActiveRecord::Base
include ROXML
include Diaspora::Webhooks
include Diaspora::Federated::Base
belongs_to :person
......@@ -15,6 +14,7 @@ class AccountDeletion < ActiveRecord::Base
xml_name :account_deletion
xml_attr :diaspora_handle
def person=(person)
self[:diaspora_handle] = person.diaspora_handle
self[:person_id] = person.id
......
......@@ -3,9 +3,9 @@
# the COPYRIGHT file.
class Comment < ActiveRecord::Base
include ROXML
include Diaspora::Webhooks
include Diaspora::Federated::Base
include Diaspora::Guid
include Diaspora::Relayable
......
class Conversation < ActiveRecord::Base
include ROXML
include Diaspora::Federated::Base
include Diaspora::Guid
include Diaspora::Webhooks
xml_attr :subject
xml_attr :created_at
......
class NotVisibleError < RuntimeError; end
class Message < ActiveRecord::Base
include ROXML
include Diaspora::Federated::Base
include Diaspora::Guid
include Diaspora::Webhooks
include Diaspora::Relayable
xml_attr :text
......
......@@ -5,10 +5,12 @@
class Photo < ActiveRecord::Base
require 'carrierwave/orm/activerecord'
include Diaspora::Federated::Shareable
include Diaspora::Commentable
include Diaspora::Shareable
# NOTE API V1 to be extracted
acts_as_api
api_accessible :backbone do |t|
......
......@@ -5,10 +5,13 @@
class Post < ActiveRecord::Base
include ApplicationHelper
include Diaspora::Federated::Shareable
include Diaspora::Likeable
include Diaspora::Commentable
include Diaspora::Shareable
has_many :participations, :dependent => :delete_all, :as => :target
attr_accessor :user_like,
......
......@@ -3,10 +3,8 @@
# the COPYRIGHT file.
class Profile < ActiveRecord::Base
require File.join(Rails.root, 'lib/diaspora/webhooks')
include Diaspora::Webhooks
include Diaspora::Federated::Base
include Diaspora::Taggable
include ROXML
attr_accessor :tag_string
......
......@@ -4,8 +4,7 @@
# the COPYRIGHT file.
class Request
include ROXML
include Diaspora::Webhooks
include Diaspora::Federated::Base
include ActiveModel::Validations
attr_accessor :sender, :recipient, :aspect
......
......@@ -3,8 +3,7 @@
# the COPYRIGHT file.
class Retraction
include ROXML
include Diaspora::Webhooks
include Diaspora::Federated::Base
xml_accessor :post_guid
xml_accessor :diaspora_handle
......
......@@ -3,8 +3,8 @@
# the COPYRIGHT file.
class SignedRetraction
include ROXML
include Diaspora::Webhooks
include Diaspora::Federated::Base
include Diaspora::Encryptable
xml_name :signed_retraction
......
......@@ -4,5 +4,4 @@
module Diaspora
autoload :Parser
autoload :Webhooks
end
# Copyright (c) 2010-2012, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
#the base level federation contract, which right now means that the object
#can be serialized and deserialized from xml, and respond to methods
#in the federation flow
#including this module lets you federate an object at the most basic of level
require 'builder/xchar'
module Diaspora
module Federated
module Base
def self.included(model)
model.instance_eval do
include ROXML
include Diaspora::Federated::Base::InstanceMethods
end
end
module InstanceMethods
def to_diaspora_xml
<<-XML
<XML>
<post>#{to_xml.to_s}</post>
</XML>
XML
end
def x(input)
input.to_s.to_xs
end
# @abstract
# @note this must return [Array<Person>]
# @return [Array<Person>]
def subscribers(user)
raise 'You must override subscribers in order to enable federation on this model'
end
# @abstract
def receive(user, person)
raise 'You must override receive in order to enable federation on this model'
end
# @param [User] sender
# @note this is a hook(optional)
def after_dispatch(sender)
end
end
end
end
end
# Copyright (c) 2012, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
#this module attempts to be what you need to mix into
# base level federation objects that are not relayable, and not persistable
#assumes there is an author, author_id, id,
module Diaspora
module Federated
module Shareable
def self.included(model)
model.instance_eval do
#we are order dependant so you don't have to be!
include Diaspora::Federated::Base
include Diaspora::Federated::Shareable::InstanceMethods
include Diaspora::Guid
xml_attr :diaspora_handle
xml_attr :public
xml_attr :created_at
end
end
module InstanceMethods
def diaspora_handle
read_attribute(:diaspora_handle) || self.author.diaspora_handle
end
def diaspora_handle=(author_handle)
self.author = Person.where(:diaspora_handle => author_handle).first
write_attribute(:diaspora_handle, author_handle)
end
# @param [User] user The user that is receiving this shareable.
# @param [Person] person The person who dispatched this shareable to the
# @return [void]
def receive(user, person)
#exists locally, but you dont know about it
#does not exsist locally, and you dont know about it
#exists_locally?
#you know about it, and it is mutable
#you know about it, and it is not mutable
self.class.transaction do
local_shareable = persisted_shareable
if local_shareable && verify_persisted_shareable(local_shareable)
self.receive_persisted(user, person, local_shareable)
elsif !local_shareable
self.receive_non_persisted(user, person)
else
Rails.logger.info("event=receive payload_type=#{self.class} update=true status=abort sender=#{self.diaspora_handle} reason='update not from shareable owner' existing_shareable=#{self.id}")
false
end
end
end
# The list of people that should receive this Shareable.
#
# @param [User] user The context, or dispatching user.
# @return [Array<Person>] The list of subscribers to this shareable
def subscribers(user)
if self.public?
user.contact_people
else
user.people_in_aspects(user.aspects_with_shareable(self.class, self.id))
end
end
protected
# @return [Shareable,void]
def persisted_shareable
self.class.where(:guid => self.guid).first
end
# @return [Boolean]
def verify_persisted_shareable(persisted_shareable)
persisted_shareable.author_id == self.author_id
end
def receive_persisted(user, person, local_shareable)
known_shareable = user.find_visible_shareable_by_id(self.class.base_class, self.guid, :key => :guid)
if known_shareable
if known_shareable.mutable?
known_shareable.update_attributes(self.attributes)
true
else
Rails.logger.info("event=receive payload_type=#{self.class} update=true status=abort sender=#{self.diaspora_handle} reason=immutable") #existing_shareable=#{known_shareable.id}")
false
end
else
user.contact_for(person).receive_shareable(local_shareable)
user.notify_if_mentioned(local_shareable)
Rails.logger.info("event=receive payload_type=#{self.class} update=true status=complete sender=#{self.diaspora_handle}") #existing_shareable=#{local_shareable.id}")
true
end
end
def receive_non_persisted(user, person)
if self.save
user.contact_for(person).receive_shareable(self)
user.notify_if_mentioned(self)
Rails.logger.info("event=receive payload_type=#{self.class} update=false status=complete sender=#{self.diaspora_handle}")
true
else
Rails.logger.info("event=receive payload_type=#{self.class} update=false status=abort sender=#{self.diaspora_handle} reason=#{self.errors.full_messages}")
false
end
end
end
end
end
end
\ No newline at end of file
#implicitly requires roxml
module Diaspora::Guid
# Creates a before_create callback which calls #set_guid and makes the guid serialize in to_xml
def self.included(model)
model.class_eval do
before_create :set_guid
xml_attr :guid
validates :guid, :uniqueness => true
end
end
......
# Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
module Diaspora
class Director
def initialize
@structure = [:create_headers, :create_endpoints, :create_subject,
:create_body, :create_footer]
end
def build(builder)
@structure.inject("") do |xml, method|
xml << builder.send(method) if builder.respond_to? method
end
end
end
class OstatusBuilder
include Diaspora::Webhooks
include PeopleHelper
def initialize(user, posts)
@user = user
@posts = posts
end
def create_headers
<<-XML
<?xml version="1.0" encoding="UTF-8"?>
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:georss="http://www.georss.org/georss" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:media="http://purl.org/syndication/atommedia" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:ostatus="http://ostatus.org/schema/1.0" xmlns:statusnet="http://status.net/schema/api/1/">
<generator uri="#{AppConfig[:pod_url]}">Diaspora</generator>
<id>#{@user.public_url}.atom</id>
<title>#{x(@user.name)}'s Public Feed</title>
<subtitle>Updates from #{x(@user.name)} on Diaspora</subtitle>
<logo>#{@user.person.profile.image_url(:thumb_small)}</logo>
<updated>#{Time.now.xmlschema}</updated>
XML
end
def create_subject
<<-XML
<author>
<activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
<name>#{x(@user.name)}</name>
<uri>#{local_or_remote_person_path(@user.person, :absolute => true)}</uri>
<poco:preferredUsername>#{x(@user.username)}</poco:preferredUsername>
<poco:displayName>#{x(@user.person.name)}</poco:displayName>
</author>
XML
end
def create_endpoints
<<-XML
<link rel="alternate" type="text/html" href="#{@user.public_url}" />
<link rel="avatar" type="image/jpeg" media:width="100" media:height="100" href="#{@user.profile.image_url}"/>
<link href="#{AppConfig[:pubsub_server]}" rel="hub"/>
<link href="#{@user.public_url}.atom" rel="self" type="application/atom+xml"/>
XML
end
def create_body
@posts.inject("") do |xml,curr|
if curr.respond_to?(:to_activity)
xml + curr.to_activity(:author => @user.person)
else
xml
end
end
end
def create_footer
<<-XML
</feed>
XML
end
end
end
# Copyright (c) 2010, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
#the pont of this object is to centralize the simmilarities of Photo and post,
# as they used to be the same class
module Diaspora
module Shareable
include Diaspora::Webhooks
def self.included(model)
model.instance_eval do
include ROXML
include Diaspora::Guid
has_many :aspect_visibilities, :as => :shareable
has_many :aspects, :through => :aspect_visibilities
......@@ -19,7 +16,6 @@ module Diaspora
belongs_to :author, :class_name => 'Person'
validates :guid, :uniqueness => true
#scopes
scope :all_public, where(:public => true, :pending => false)
......@@ -27,9 +23,10 @@ module Diaspora
def self.owned_or_visible_by_user(user)
self.joins("LEFT OUTER JOIN share_visibilities ON share_visibilities.shareable_id = posts.id AND share_visibilities.shareable_type = 'Post'").
joins("LEFT OUTER JOIN contacts ON contacts.id = share_visibilities.contact_id").
where(Contact.arel_table[:user_id].eq(user.id).or(
self.arel_table[:public].eq(true).or(
self.arel_table[:author_id].eq(user.person.id)
where(
Contact.arel_table[:user_id].eq(user.id).or(
self.arel_table[:public].eq(true).or(
self.arel_table[:author_id].eq(user.person.id)
)
)
).
......@@ -45,56 +42,6 @@ module Diaspora
def self.by_max_time(max_time, order='created_at')
where("#{self.table_name}.#{order} < ?", max_time).order("#{self.table_name}.#{order} desc")
end
xml_attr :diaspora_handle
xml_attr :public
xml_attr :created_at
end
end
def diaspora_handle
read_attribute(:diaspora_handle) || self.author.diaspora_handle
end
def diaspora_handle= nd
self.author = Person.where(:diaspora_handle => nd).first
write_attribute(:diaspora_handle, nd)
end
# @param [User] user The user that is receiving this shareable.
# @param [Person] person The person who dispatched this shareable to the
# @return [void]
def receive(user, person)
#exists locally, but you dont know about it
#does not exsist locally, and you dont know about it
#exists_locally?
#you know about it, and it is mutable
#you know about it, and it is not mutable
self.class.transaction do
local_shareable = persisted_shareable
if local_shareable && verify_persisted_shareable(local_shareable)
self.receive_persisted(user, person, local_shareable)
elsif !local_shareable
self.receive_non_persisted(user, person)
else
Rails.logger.info("event=receive payload_type=#{self.class} update=true status=abort sender=#{self.diaspora_handle} reason='update not from shareable owner' existing_shareable=#{self.id}")
false
end
end
end
# The list of people that should receive this Shareable.
#
# @param [User] user The context, or dispatching user.
# @return [Array<Person>] The list of subscribers to this shareable
def subscribers(user)
if self.public?
user.contact_people
else
user.people_in_aspects(user.aspects_with_shareable(self.class, self.id))
end
end
......@@ -103,48 +50,5 @@ module Diaspora
self.class.where(:id => self.id).
update_all(:reshares_count => self.reshares.count)
end
protected
# @return [Shareable,void]
def persisted_shareable
self.class.where(:guid => self.guid).first
end
# @return [Boolean]
def verify_persisted_shareable(persisted_shareable)
persisted_shareable.author_id == self.author_id
end
def receive_persisted(user, person, local_shareable)
known_shareable = user.find_visible_shareable_by_id(self.class.base_class, self.guid, :key => :guid)
if known_shareable
if known_shareable.mutable?
known_shareable.update_attributes(self.attributes)
true
else
Rails.logger.info("event=receive payload_type=#{self.class} update=true status=abort sender=#{self.diaspora_handle} reason=immutable") #existing_shareable=#{known_shareable.id}")
false
end
else
user.contact_for(person).receive_shareable(local_shareable)
user.notify_if_mentioned(local_shareable)
Rails.logger.info("event=receive payload_type=#{self.class} update=true status=complete sender=#{self.diaspora_handle}") #existing_shareable=#{local_shareable.id}")
true
end
end
def receive_non_persisted(user, person)
if self.save
user.contact_for(person).receive_shareable(self)
user.notify_if_mentioned(self)
Rails.logger.info("event=receive payload_type=#{self.class} update=false status=complete sender=#{self.diaspora_handle}")
true
else
Rails.logger.info("event=receive payload_type=#{self.class} update=false status=abort sender=#{self.diaspora_handle} reason=#{self.errors.full_messages}")
false
end
end
end
end
# Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
module Diaspora
module Webhooks
require 'builder/xchar'
def to_diaspora_xml
<<XML
<XML>
<post>#{to_xml.to_s}</post>
</XML>
XML
end
def x(input)
input.to_s.to_xs
end
# @abstract
# @note this must return [Array<Person>]
# @return [Array<Person>]
def subscribers(user)
raise 'You must override subscribers in order to enable federation on this model'
end
# @abstract
def receive(user, person)
raise 'You must override receive in order to enable federation on this model'
end
# @param [User] sender
# @note this is a hook
def after_dispatch sender
end
end
end
......@@ -3,9 +3,7 @@ module Federated
self.abstract_class = true
#crazy ordering issues - DEATH TO ROXML
include ROXML
include Diaspora::Webhooks
include Diaspora::Federated::Base
include Diaspora::Guid
#seriously, don't try to move this shit around until you have killed ROXML
......
......@@ -25,7 +25,7 @@ class Postzord::Dispatcher
# @return [Postzord::Dispatcher] Public or private dispatcher depending on the object's intended audience
def self.build(user, object, opts={})
unless object.respond_to? :to_diaspora_xml
raise 'This object does not respond_to? to_diaspora xml. Try including Diaspora::Webhooks into your object'
raise 'This object does not respond_to? to_diaspora xml. Try including Diaspora::Federated::Base into your object'
end
if self.object_should_be_processed_as_public?(object)
......
#dispatching
#a class that figures out the markup of an object
class Federated::Presenter
end
#a class that detects the audience of a post
class Federated::Audience
end
#this class dispatchs the post to services, like facebook, twitter, etc
class ServiceDispatcher
#interacts with a single post, and many provided services
end
#Receiving Phases
#receive request => check author
#xml payload
#decode
#convert to meta object
#perfom various validations
#recieve! <= save object, if applicable
#after_receive hook
Ideas:
Federated objects are delegated stubs of real objects, with converstion constuctors
- this turns receive to "make model level object from payload"
- seperate validations and checking with persistance and noticiation layer
http => deserliaization/decoding/descrypting => meta object => validations => receive => cleanup
......@@ -47,12 +47,10 @@ describe UsersController do
end
it 'redirects to a profile page if html is requested' do