diff --git a/docs/_entities/event.md b/docs/_entities/event.md new file mode 100644 index 0000000..3328267 --- /dev/null +++ b/docs/_entities/event.md @@ -0,0 +1,78 @@ +--- +title: Event +--- + +This entity represents an event. + +See also: [EventParticipation][event_participation] + +## Properties + +| Property | Type (Length) | Description | +| --------- | ---------------------------- | --------------------------------------------- | +| `author` | [diaspora\* ID][diaspora-id] | The diaspora\* ID of the author of the event. | +| `guid` | [GUID][guid] | The GUID of the event. | +| `summary` | [String][string] (255) | The summary of the event. | +| `start` | [Timestamp][timestamp] | The start time of the event (in UTC). | + +## Optional Properties + +| Property | Type (Length) | Description | +| ------------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `end` | [Timestamp][timestamp] | The end time of the event (in UTC). If missing it is an open-end or a single `all_day` event. | +| `all_day` | [Boolean][boolean] | `true` if it is an all day event. Time/timezone is ignored. `false` by default. | +| `timezone` | [Timezone][timezone] | If the event is fixed to a specific timezone, this can be set. The `start`/`end` timestamps are then displayed in this timezone. This is useful for local events. If missing or empty the timestamps are displayed in the timezone of the user. | +| `description` | [Markdown][markdown] (65535) | Description of the event. | +| `location` | [Location][location] | Location of the event. | + +## Examples + +### With start, end and timezone + +~~~xml + + alice@example.org + bb8371f0b1c901342ebd55853a9b5d75 + Cool event + 2016-12-27T12:00:00Z + 2016-12-27T13:00:00Z + false + Europe/Berlin + You need to see this! + +
Vienna, Austria
+ 48.208174 + 16.373819 +
+
+~~~ + +### All day event + +~~~xml + + alice@example.org + bb8371f0b1c901342ebd55853a9b5d75 + Cool event + 2016-12-27T00:00:00Z + + true + + You need to see this! + +
Vienna, Austria
+ 48.208174 + 16.373819 +
+
+~~~ + +[event_participation]: {{ site.baseurl }}/entities/event_participation.html +[diaspora-id]: {{ site.baseurl }}/federation/types.html#diaspora-id +[guid]: {{ site.baseurl }}/federation/types.html#guid +[string]: {{ site.baseurl }}/federation/types.html#string +[timestamp]: {{ site.baseurl }}/federation/types.html#timestamp +[markdown]: {{ site.baseurl }}/federation/types.html#markdown +[boolean]: {{ site.baseurl }}/federation/types.html#boolean +[timezone]: {{ site.baseurl }}/federation/types.html#timezone +[location]: {{ site.baseurl }}/entities/location.html diff --git a/docs/_entities/event_participation.md b/docs/_entities/event_participation.md new file mode 100644 index 0000000..177d715 --- /dev/null +++ b/docs/_entities/event_participation.md @@ -0,0 +1,54 @@ +--- +title: EventParticipation +--- + +This entity represents a participation in an [Event][event]. + +See also: [Relayable][relayable] + +## Properties + +| Property | Type | Description | +| ------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `author` | [diaspora\* ID][diaspora-id] | The diaspora\* ID of the author of the event participation. | +| `guid` | [GUID][guid] | The GUID of the event participation. | +| `parent_guid` | [GUID][guid] | The GUID of the [Event][event]. | +| `status` | [String][string] | The participation status, lowercase string as defined in [RFC 5545, Section 3.2.12][status] (`accepted`, `declined` or `tentative`). | +| `author_signature` | [Signature][signature] | The signature from the author of the event participation. | +| `parent_author_signature` | [Signature][signature] | The signature from the author of the [Event][event]. | + +## Examples + +### From author + +~~~xml + + alice@example.org + 92f26ff0b1cb01342ebd55853a9b5d75 + bb8371f0b1c901342ebd55853a9b5d75 + accepted + dT6KbT7kp0bE+s3//ZErxO1wvVIqtD0lY67i81+dO43B4D2m5kjCdzW240eWt/jZmcHIsdxXf4WHNdrb6ZDnamA8I1FUVnLjHA9xexBITQsSLXrcV88UdammSmmOxl1Ac4VUXqFpdavm6a7/MwOJ7+JHP8TbUO9siN+hMfgUbtY= + + +~~~ + +### From parent author + +~~~xml + + alice@example.org + 92f26ff0b1cb01342ebd55853a9b5d75 + bb8371f0b1c901342ebd55853a9b5d75 + accepted + dT6KbT7kp0bE+s3//ZErxO1wvVIqtD0lY67i81+dO43B4D2m5kjCdzW240eWt/jZmcHIsdxXf4WHNdrb6ZDnamA8I1FUVnLjHA9xexBITQsSLXrcV88UdammSmmOxl1Ac4VUXqFpdavm6a7/MwOJ7+JHP8TbUO9siN+hMfgUbtY= + gWasNPpSnMcKBIMWyzfoVO6sr8eRYkhUqy3PIkkh53n/ki+DM9mnh3ayotI0+6un9aq1N3XkS7Vn05ZD3+nHVby6i21XkYgPnbD8pWYuBBj7VGPyahT70BUs/vSvY8KX8V3wYfsPsaiAgJsAFg2UHYdY3r4/oWdIIbBZc21O3zk= + +~~~ + +[diaspora-id]: {{ site.baseurl }}/federation/types.html#diaspora-id +[guid]: {{ site.baseurl }}/federation/types.html#guid +[string]: {{ site.baseurl }}/federation/types.html#string +[status]: https://tools.ietf.org/html/rfc5545#section-3.2.12 +[signature]: {{ site.baseurl }}/federation/types.html#signature +[event]: {{ site.baseurl }}/entities/event.html +[relayable]: {{ site.baseurl }}/federation/relayable.html diff --git a/docs/_entities/status_message.md b/docs/_entities/status_message.md index c6715e9..1302f4f 100644 --- a/docs/_entities/status_message.md +++ b/docs/_entities/status_message.md @@ -22,6 +22,7 @@ This entity represents a reshare of a status message. It inherits from [Post][po | `location` | [Location][location] | The Location information of the status message. | | `photo` | [Photo][photo]s | The attached Photos of the status message, the `status_message_guid` and the `author` need to match the status message. | | `poll` | [Poll][poll] | The attached Poll of the status message. | +| `event` | [Event][event] | The attached Event of the status message. | ## Examples @@ -183,3 +184,4 @@ This entity represents a reshare of a status message. It inherits from [Post][po [location]: {{ site.baseurl }}/entities/location.html [photo]: {{ site.baseurl }}/entities/photo.html [poll]: {{ site.baseurl }}/entities/poll.html +[event]: {{ site.baseurl }}/entities/event.html diff --git a/docs/federation/types.md b/docs/federation/types.md index aa09c5d..4d1dcd5 100644 --- a/docs/federation/types.md +++ b/docs/federation/types.md @@ -85,6 +85,12 @@ An [ISO 8601][iso8601] date. Example: `2016-02-19` +## Timezone + +A timezone in the form `Area/Location` as used in the [Time Zone Database][tz]. + +Example: `Europe/Berlin` + ## Signature Signature with the private RSA key using the RSA-SHA256 algorithm and base64-encoded. @@ -98,3 +104,4 @@ Example: [entities]: {{ site.baseurl }}/entities/ [commonmark]: http://spec.commonmark.org/ [iso8601]: https://www.w3.org/TR/NOTE-datetime +[tz]: https://www.iana.org/time-zones diff --git a/lib/diaspora_federation/entities.rb b/lib/diaspora_federation/entities.rb index 343e502..6760c56 100644 --- a/lib/diaspora_federation/entities.rb +++ b/lib/diaspora_federation/entities.rb @@ -28,6 +28,10 @@ require "diaspora_federation/entities/poll" require "diaspora_federation/entities/poll_participation" require "diaspora_federation/entities/location" + +require "diaspora_federation/entities/event" +require "diaspora_federation/entities/event_participation" + require "diaspora_federation/entities/photo" require "diaspora_federation/entities/status_message" require "diaspora_federation/entities/reshare" diff --git a/lib/diaspora_federation/entities/event.rb b/lib/diaspora_federation/entities/event.rb new file mode 100644 index 0000000..4115b8c --- /dev/null +++ b/lib/diaspora_federation/entities/event.rb @@ -0,0 +1,55 @@ +module DiasporaFederation + module Entities + # This entity represents an event and it is federated as a part of a status message. + # + # @see Validators::EventValidator + class Event < Entity + # @!attribute [r] author + # The diaspora* ID of the person who created the event + # @see Person#author + # @return [String] author diaspora* ID + property :author, :string + + # @!attribute [r] guid + # A random string of at least 16 chars + # @see Validation::Rule::Guid + # @return [String] guid + property :guid, :string + + # @!attribute [r] summary + # The summary of the event + # @return [String] event summary + property :summary, :string + + # @!attribute [r] description + # Description of the event + # @return [String] event description + property :description, :string, default: nil + + # @!attribute [r] start + # The start time of the event + # @return [String] event start + property :start, :timestamp + + # @!attribute [r] end + # The end time of the event + # @return [String] event end + property :end, :timestamp, default: nil + + # @!attribute [r] all_day + # Points if the event is an all day event + # @return [Boolean] is it an all day event + property :all_day, :boolean, default: false + + # @!attribute [r] timezone + # Timezone to which the event is fixed to + # @return [String] timezone + property :timezone, :string, default: nil + + # @!attribute [r] location + # Location of the event + # @return [Entities::Location] location + entity :location, Entities::Location, default: nil + end + end +end diff --git a/lib/diaspora_federation/entities/event_participation.rb b/lib/diaspora_federation/entities/event_participation.rb new file mode 100644 index 0000000..4c2dc22 --- /dev/null +++ b/lib/diaspora_federation/entities/event_participation.rb @@ -0,0 +1,27 @@ +module DiasporaFederation + module Entities + # This entity represents a participation in an event, i.e. it is issued when a user responds to en event. + # + # @see Validators::EventParticipationValidator + class EventParticipation < Entity + # Old signature order + # @deprecated + LEGACY_SIGNATURE_ORDER = %i(author guid parent_guid status).freeze + + # The {EventParticipation} parent is an {Event} + PARENT_TYPE = "Event".freeze + + include Relayable + + # Redefine the author property without +diaspora_handle+ +xml_name+ + # @deprecated Can be removed after XMLs are generated with new names + property :author, :string + + # @!attribute [r] status + # The participation status of the user + # "accepted", "declined" or "tentative" + # @return [String] event participation status + property :status, :string + end + end +end diff --git a/lib/diaspora_federation/entities/status_message.rb b/lib/diaspora_federation/entities/status_message.rb index 555b8b5..7b9494d 100644 --- a/lib/diaspora_federation/entities/status_message.rb +++ b/lib/diaspora_federation/entities/status_message.rb @@ -26,6 +26,11 @@ module DiasporaFederation # @return [Entities::Poll] poll entity :poll, Entities::Poll, default: nil + # @!attribute [r] event + # Optional event attached to the status message + # @return [Entities::Event] event + entity :event, Entities::Event, default: nil + # @!attribute [r] public # Shows whether the status message is visible to everyone or only to some aspects # @return [Boolean] is it public diff --git a/lib/diaspora_federation/test/factories.rb b/lib/diaspora_federation/test/factories.rb index 4fad247..fbfa20d 100644 --- a/lib/diaspora_federation/test/factories.rb +++ b/lib/diaspora_federation/test/factories.rb @@ -200,6 +200,24 @@ module DiasporaFederation poll_answer_guid { generate(:guid) } end + factory :event_entity, class: DiasporaFederation::Entities::Event do + author { generate(:diaspora_id) } + guid + summary "Cool event" + description "You need to see this!" + start { Time.now.utc.change(min: 0).change(sec: 0).change(usec: 0) - 1.hour } + add_attribute(:end) { Time.now.utc.change(min: 0).change(sec: 0).change(usec: 0) + 1.hour } + all_day false + timezone "Europe/Berlin" + end + + factory :event_participation_entity, + class: DiasporaFederation::Entities::EventParticipation, parent: :relayable_entity do + author { generate(:diaspora_id) } + guid + status "accepted" + end + factory :related_entity, class: DiasporaFederation::Entities::RelatedEntity do author { generate(:diaspora_id) } local true diff --git a/lib/diaspora_federation/validators.rb b/lib/diaspora_federation/validators.rb index e9c54f5..6fea7e4 100644 --- a/lib/diaspora_federation/validators.rb +++ b/lib/diaspora_federation/validators.rb @@ -44,6 +44,8 @@ require "diaspora_federation/validators/account_deletion_validator" require "diaspora_federation/validators/comment_validator" require "diaspora_federation/validators/contact_validator" require "diaspora_federation/validators/conversation_validator" +require "diaspora_federation/validators/event_participation_validator" +require "diaspora_federation/validators/event_validator" require "diaspora_federation/validators/h_card_validator" require "diaspora_federation/validators/like_validator" require "diaspora_federation/validators/location_validator" diff --git a/lib/diaspora_federation/validators/event_participation_validator.rb b/lib/diaspora_federation/validators/event_participation_validator.rb new file mode 100644 index 0000000..f7588a4 --- /dev/null +++ b/lib/diaspora_federation/validators/event_participation_validator.rb @@ -0,0 +1,12 @@ +module DiasporaFederation + module Validators + # This validates a {Entities::EventParticipation}. + class EventParticipationValidator < Validation::Validator + include Validation + + include RelayableValidator + + rule :status, regular_expression: {regex: /\A(accepted|declined|tentative)\z/} + end + end +end diff --git a/lib/diaspora_federation/validators/event_validator.rb b/lib/diaspora_federation/validators/event_validator.rb new file mode 100644 index 0000000..f6a20c8 --- /dev/null +++ b/lib/diaspora_federation/validators/event_validator.rb @@ -0,0 +1,21 @@ +module DiasporaFederation + module Validators + # This validates a {Entities::Event}. + class EventValidator < Validation::Validator + include Validation + + rule :author, %i(not_empty diaspora_id) + + rule :guid, :guid + + rule :summary, [:not_empty, length: {maximum: 255}] + rule :description, length: {maximum: 65_535} + + rule :start, :not_nil + + rule :all_day, :boolean + + rule :timezone, regular_expression: {regex: %r{\A[A-Za-z_-]{,14}(/[A-Za-z_-]{,14}){1,2}\z}} + end + end +end diff --git a/spec/factories.rb b/spec/factories.rb index 3dc7213..b85508f 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -39,6 +39,12 @@ FactoryGirl.define do after(:create, &:save) end + factory :event, class: Entity do + entity_type "Event" + author { FactoryGirl.build(:person) } + after(:create, &:save) + end + factory :conversation, class: Entity do entity_type "Conversation" author { FactoryGirl.build(:person) } diff --git a/spec/lib/diaspora_federation/entities/event_participation_spec.rb b/spec/lib/diaspora_federation/entities/event_participation_spec.rb new file mode 100644 index 0000000..d743614 --- /dev/null +++ b/spec/lib/diaspora_federation/entities/event_participation_spec.rb @@ -0,0 +1,35 @@ +module DiasporaFederation + describe Entities::EventParticipation do + let(:parent) { FactoryGirl.create(:event, author: bob) } + let(:parent_entity) { FactoryGirl.build(:related_entity, author: bob.diaspora_id) } + let(:data) { + add_signatures( + FactoryGirl.build( + :event_participation_entity, + author: alice.diaspora_id, + parent_guid: parent.guid, + parent: parent_entity + ) + ) + } + + let(:xml) { <<-XML } + + #{data[:author]} + #{data[:guid]} + #{parent.guid} + #{data[:status]} + #{data[:author_signature]} + #{data[:parent_author_signature]} + +XML + + let(:string) { "EventParticipation:#{data[:guid]}:#{parent.guid}" } + + it_behaves_like "an Entity subclass" + + it_behaves_like "an XML Entity" + + it_behaves_like "a relayable Entity" + end +end diff --git a/spec/lib/diaspora_federation/entities/event_spec.rb b/spec/lib/diaspora_federation/entities/event_spec.rb new file mode 100644 index 0000000..0507ae2 --- /dev/null +++ b/spec/lib/diaspora_federation/entities/event_spec.rb @@ -0,0 +1,52 @@ +module DiasporaFederation + describe Entities::Event do + let(:location) { FactoryGirl.build(:location_entity) } + let(:data) { + FactoryGirl.attributes_for(:event_entity).merge(author: alice.diaspora_id, location: location) + } + + let(:xml) { <<-XML } + + #{data[:author]} + #{data[:guid]} + #{data[:summary]} + #{data[:description]} + #{data[:start].utc.iso8601} + #{data[:end].utc.iso8601} + #{data[:all_day]} + #{data[:timezone]} + +
#{location.address}
+ #{location.lat} + #{location.lng} +
+
+XML + + let(:string) { "Event:#{data[:guid]}" } + + it_behaves_like "an Entity subclass" + + it_behaves_like "an XML Entity" + + context "default values" do + it "uses default values" do + minimal_xml = <<-XML + + #{data[:author]} + #{data[:guid]} + #{data[:summary]} + #{data[:start].utc.iso8601} + +XML + + parsed_instance = DiasporaFederation::Salmon::XmlPayload.unpack(Nokogiri::XML::Document.parse(minimal_xml).root) + expect(parsed_instance.end).to be_nil + expect(parsed_instance.all_day).to be_falsey + expect(parsed_instance.timezone).to be_nil + expect(parsed_instance.description).to be_nil + expect(parsed_instance.location).to be_nil + end + end + end +end diff --git a/spec/lib/diaspora_federation/entities/status_message_spec.rb b/spec/lib/diaspora_federation/entities/status_message_spec.rb index 25b7473..1f0ca4a 100644 --- a/spec/lib/diaspora_federation/entities/status_message_spec.rb +++ b/spec/lib/diaspora_federation/entities/status_message_spec.rb @@ -9,6 +9,7 @@ module DiasporaFederation photos: [photo1, photo2], location: location, poll: nil, + event: nil, provider_display_name: "something" ) } diff --git a/spec/lib/diaspora_federation/validators/event_participation_validator_spec.rb b/spec/lib/diaspora_federation/validators/event_participation_validator_spec.rb new file mode 100644 index 0000000..3296d86 --- /dev/null +++ b/spec/lib/diaspora_federation/validators/event_participation_validator_spec.rb @@ -0,0 +1,17 @@ +module DiasporaFederation + describe Validators::EventParticipationValidator do + let(:entity) { :event_participation_entity } + + it_behaves_like "a common validator" + + it_behaves_like "a relayable validator" + + describe "#status" do + it_behaves_like "a property with a value validation/restriction" do + let(:property) { :status } + let(:wrong_values) { ["", "yes", "foobar"] } + let(:correct_values) { %w(accepted declined tentative) } + end + end + end +end diff --git a/spec/lib/diaspora_federation/validators/event_validator_spec.rb b/spec/lib/diaspora_federation/validators/event_validator_spec.rb new file mode 100644 index 0000000..1bd3864 --- /dev/null +++ b/spec/lib/diaspora_federation/validators/event_validator_spec.rb @@ -0,0 +1,62 @@ +module DiasporaFederation + describe Validators::EventValidator do + let(:entity) { :event_entity } + + it_behaves_like "a common validator" + + it_behaves_like "a diaspora* ID validator" do + let(:property) { :author } + let(:mandatory) { true } + end + + it_behaves_like "a guid validator" do + let(:property) { :guid } + end + + describe "#summary" do + it_behaves_like "a property with a value validation/restriction" do + let(:property) { :summary } + let(:wrong_values) { ["a" * 256, nil, ""] } + let(:correct_values) { ["a" * 255] } + end + end + + describe "#description" do + it_behaves_like "a property with a value validation/restriction" do + let(:property) { :description } + let(:wrong_values) { ["a" * 65_536] } + let(:correct_values) { ["a" * 65_535, nil, ""] } + end + end + + describe "#start" do + it_behaves_like "a property with a value validation/restriction" do + let(:property) { :start } + let(:wrong_values) { [nil] } + let(:correct_values) { [Time.now.utc] } + end + end + + describe "#end" do + it_behaves_like "a property with a value validation/restriction" do + let(:property) { :end } + let(:wrong_values) { [] } + let(:correct_values) { [nil, Time.now.utc] } + end + end + + describe "#all_day" do + it_behaves_like "a boolean validator" do + let(:property) { :all_day } + end + end + + describe "#timezone" do + it_behaves_like "a property with a value validation/restriction" do + let(:property) { :timezone } + let(:wrong_values) { ["foobar"] } + let(:correct_values) { [nil, "Europe/Berlin", "America/Argentina/ComodRivadavia"] } + end + end + end +end