coarnotify 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/coarnotify/client.rb +88 -0
- data/lib/coarnotify/core/activity_streams2.rb +234 -0
- data/lib/coarnotify/core/notify.rb +833 -0
- data/lib/coarnotify/exceptions.rb +106 -0
- data/lib/coarnotify/factory.rb +114 -0
- data/lib/coarnotify/http.rb +148 -0
- data/lib/coarnotify/patterns/accept.rb +50 -0
- data/lib/coarnotify/patterns/announce_endorsement.rb +103 -0
- data/lib/coarnotify/patterns/announce_relationship.rb +82 -0
- data/lib/coarnotify/patterns/announce_review.rb +145 -0
- data/lib/coarnotify/patterns/announce_service_result.rb +145 -0
- data/lib/coarnotify/patterns/reject.rb +51 -0
- data/lib/coarnotify/patterns/request_endorsement.rb +83 -0
- data/lib/coarnotify/patterns/request_review.rb +71 -0
- data/lib/coarnotify/patterns/tentatively_accept.rb +51 -0
- data/lib/coarnotify/patterns/tentatively_reject.rb +40 -0
- data/lib/coarnotify/patterns/undo_offer.rb +48 -0
- data/lib/coarnotify/patterns/unprocessable_notification.rb +42 -0
- data/lib/coarnotify/server.rb +112 -0
- data/lib/coarnotify/validate.rb +256 -0
- data/lib/coarnotify/version.rb +8 -0
- data/lib/coarnotify.rb +78 -0
- metadata +121 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 0c275102a17471d02d892d5fd97cef39a6de4df156789c1b39057deffb3f83b6
|
|
4
|
+
data.tar.gz: 7667b1a6ff67dab4a44bb3c0f1faac5f5baf9e4300603bc8c82883ba7a30ebb1
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 389e163e70f37372f625466b213a2c67cbd7298d3166d064ad7636298741c6976818b0d0a3a3a85149c7e50b08b30996280d904bd73053bab9fd13087006094b
|
|
7
|
+
data.tar.gz: '018e0f9c74b240ab33719bda796ebd3644121307cda75a62c713bacd5440fee8a8bdf4c2bd00fd23052eb5bf50714ffc1473f7c0367e74487fc10ab4e08310f8'
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require_relative 'exceptions'
|
|
5
|
+
require_relative 'http'
|
|
6
|
+
require_relative 'core/notify'
|
|
7
|
+
|
|
8
|
+
module Coarnotify
|
|
9
|
+
# This module contains all the client-specific code for sending notifications
|
|
10
|
+
# to an inbox and receiving the responses it may return
|
|
11
|
+
module Client
|
|
12
|
+
# An object representing the response from a COAR Notify inbox.
|
|
13
|
+
#
|
|
14
|
+
# This contains the action that was carried out on the server:
|
|
15
|
+
#
|
|
16
|
+
# * CREATED - a new resource was created
|
|
17
|
+
# * ACCEPTED - the request was accepted, but the resource was not yet created
|
|
18
|
+
#
|
|
19
|
+
# In the event that the resource is created, then there will also be a location
|
|
20
|
+
# URL which will give you access to the resource
|
|
21
|
+
class NotifyResponse
|
|
22
|
+
CREATED = "created"
|
|
23
|
+
ACCEPTED = "accepted"
|
|
24
|
+
|
|
25
|
+
attr_reader :action, :location
|
|
26
|
+
|
|
27
|
+
# Construct a new NotifyResponse object with the action (created or accepted) and the location URL (optional)
|
|
28
|
+
#
|
|
29
|
+
# @param action [String] The action which the server said it took
|
|
30
|
+
# @param location [String, nil] The HTTP URI for the resource that was created (if present)
|
|
31
|
+
def initialize(action, location = nil)
|
|
32
|
+
@action = action
|
|
33
|
+
@location = location
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The COAR Notify Client, which is the mechanism through which you will interact with external inboxes.
|
|
38
|
+
#
|
|
39
|
+
# If you do not supply an inbox URL at construction you will
|
|
40
|
+
# need to supply it via the inbox_url= setter, or when you send a notification
|
|
41
|
+
class COARNotifyClient
|
|
42
|
+
attr_accessor :inbox_url
|
|
43
|
+
|
|
44
|
+
# Initialize the COAR Notify Client
|
|
45
|
+
#
|
|
46
|
+
# @param inbox_url [String, nil] HTTP URI of the inbox to communicate with by default
|
|
47
|
+
# @param http_layer [Http::HttpLayer, nil] An implementation of the HttpLayer interface to use for sending HTTP requests
|
|
48
|
+
def initialize(inbox_url: nil, http_layer: nil)
|
|
49
|
+
@inbox_url = inbox_url
|
|
50
|
+
@http = http_layer || Http::NetHttpLayer.new
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Send the given notification to the inbox. If no inbox URL is provided, the default inbox URL will be used.
|
|
54
|
+
#
|
|
55
|
+
# @param notification [Core::Notify::NotifyPattern] The notification object
|
|
56
|
+
# @param inbox_url [String, nil] The HTTP URI to send the notification to
|
|
57
|
+
# @param validate [Boolean] Whether to validate the notification before sending
|
|
58
|
+
# @return [NotifyResponse] a NotifyResponse object representing the response from the server
|
|
59
|
+
def send(notification, inbox_url: nil, validate: true)
|
|
60
|
+
inbox_url ||= @inbox_url
|
|
61
|
+
inbox_url ||= notification.target&.inbox
|
|
62
|
+
|
|
63
|
+
raise ArgumentError, "No inbox URL provided at the client, method, or notification level" if inbox_url.nil?
|
|
64
|
+
|
|
65
|
+
if validate
|
|
66
|
+
begin
|
|
67
|
+
notification.validate
|
|
68
|
+
rescue ValidationError => e
|
|
69
|
+
raise NotifyException, "Attempting to send invalid notification; to override set validate: false when calling this method"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
resp = @http.post(inbox_url,
|
|
74
|
+
JSON.generate(notification.to_jsonld),
|
|
75
|
+
{ "Content-Type" => "application/ld+json;profile=\"https://www.w3.org/ns/activitystreams\"" })
|
|
76
|
+
|
|
77
|
+
case resp.status_code
|
|
78
|
+
when 201
|
|
79
|
+
NotifyResponse.new(NotifyResponse::CREATED, resp.header("Location"))
|
|
80
|
+
when 202
|
|
81
|
+
NotifyResponse.new(NotifyResponse::ACCEPTED)
|
|
82
|
+
else
|
|
83
|
+
raise NotifyException, "Unexpected response: #{resp.status_code}"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Coarnotify
|
|
6
|
+
module Core
|
|
7
|
+
# This module contains everything COAR Notify needs to know about ActivityStreams 2.0
|
|
8
|
+
# https://www.w3.org/TR/activitystreams-core/
|
|
9
|
+
#
|
|
10
|
+
# It provides knowledge of the essential AS properties and types, and a class to wrap
|
|
11
|
+
# ActivityStreams objects and provide a simple interface to work with them.
|
|
12
|
+
#
|
|
13
|
+
# **NOTE** this is not a complete implementation of AS 2.0, it is **only** what is required
|
|
14
|
+
# to work with COAR Notify patterns.
|
|
15
|
+
module ActivityStreams2
|
|
16
|
+
# Namespace for Activity Streams, to be used to construct namespaced properties used in COAR Notify Patterns
|
|
17
|
+
ACTIVITY_STREAMS_NAMESPACE = "https://www.w3.org/ns/activitystreams"
|
|
18
|
+
|
|
19
|
+
# ActivityStreams 2.0 properties used in COAR Notify Patterns
|
|
20
|
+
#
|
|
21
|
+
# These are provided as arrays, where the first element is the property name, and the second element is the namespace.
|
|
22
|
+
#
|
|
23
|
+
# These are suitable to be used as property names in all the property getters/setters in the notify pattern objects
|
|
24
|
+
# and in the validation configuration.
|
|
25
|
+
module Properties
|
|
26
|
+
# id property
|
|
27
|
+
ID = ["id", ACTIVITY_STREAMS_NAMESPACE].freeze
|
|
28
|
+
|
|
29
|
+
# type property
|
|
30
|
+
TYPE = ["type", ACTIVITY_STREAMS_NAMESPACE].freeze
|
|
31
|
+
|
|
32
|
+
# origin property
|
|
33
|
+
ORIGIN = ["origin", ACTIVITY_STREAMS_NAMESPACE].freeze
|
|
34
|
+
|
|
35
|
+
# object property
|
|
36
|
+
OBJECT = ["object", ACTIVITY_STREAMS_NAMESPACE].freeze
|
|
37
|
+
|
|
38
|
+
# target property
|
|
39
|
+
TARGET = ["target", ACTIVITY_STREAMS_NAMESPACE].freeze
|
|
40
|
+
|
|
41
|
+
# actor property
|
|
42
|
+
ACTOR = ["actor", ACTIVITY_STREAMS_NAMESPACE].freeze
|
|
43
|
+
|
|
44
|
+
# inReplyTo property
|
|
45
|
+
IN_REPLY_TO = ["inReplyTo", ACTIVITY_STREAMS_NAMESPACE].freeze
|
|
46
|
+
|
|
47
|
+
# context property
|
|
48
|
+
CONTEXT = ["context", ACTIVITY_STREAMS_NAMESPACE].freeze
|
|
49
|
+
|
|
50
|
+
# summary property
|
|
51
|
+
SUMMARY = ["summary", ACTIVITY_STREAMS_NAMESPACE].freeze
|
|
52
|
+
|
|
53
|
+
# as:subject property
|
|
54
|
+
SUBJECT_TRIPLE = ["as:subject", ACTIVITY_STREAMS_NAMESPACE].freeze
|
|
55
|
+
|
|
56
|
+
# as:object property
|
|
57
|
+
OBJECT_TRIPLE = ["as:object", ACTIVITY_STREAMS_NAMESPACE].freeze
|
|
58
|
+
|
|
59
|
+
# as:relationship property
|
|
60
|
+
RELATIONSHIP_TRIPLE = ["as:relationship", ACTIVITY_STREAMS_NAMESPACE].freeze
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# List of all the Activity Streams types COAR Notify may use.
|
|
64
|
+
#
|
|
65
|
+
# Note that COAR Notify also has its own custom types and they are defined in
|
|
66
|
+
# Coarnotify::Core::Notify::NotifyTypes
|
|
67
|
+
module ActivityStreamsTypes
|
|
68
|
+
# Activities
|
|
69
|
+
ACCEPT = "Accept"
|
|
70
|
+
ANNOUNCE = "Announce"
|
|
71
|
+
REJECT = "Reject"
|
|
72
|
+
OFFER = "Offer"
|
|
73
|
+
TENTATIVE_ACCEPT = "TentativeAccept"
|
|
74
|
+
TENTATIVE_REJECT = "TentativeReject"
|
|
75
|
+
FLAG = "Flag"
|
|
76
|
+
UNDO = "Undo"
|
|
77
|
+
|
|
78
|
+
# Objects
|
|
79
|
+
ACTIVITY = "Activity"
|
|
80
|
+
APPLICATION = "Application"
|
|
81
|
+
ARTICLE = "Article"
|
|
82
|
+
AUDIO = "Audio"
|
|
83
|
+
COLLECTION = "Collection"
|
|
84
|
+
COLLECTION_PAGE = "CollectionPage"
|
|
85
|
+
RELATIONSHIP = "Relationship"
|
|
86
|
+
DOCUMENT = "Document"
|
|
87
|
+
EVENT = "Event"
|
|
88
|
+
GROUP = "Group"
|
|
89
|
+
IMAGE = "Image"
|
|
90
|
+
INTRANSITIVE_ACTIVITY = "IntransitiveActivity"
|
|
91
|
+
NOTE = "Note"
|
|
92
|
+
OBJECT = "Object"
|
|
93
|
+
ORDERED_COLLECTION = "OrderedCollection"
|
|
94
|
+
ORDERED_COLLECTION_PAGE = "OrderedCollectionPage"
|
|
95
|
+
ORGANIZATION = "Organization"
|
|
96
|
+
PAGE = "Page"
|
|
97
|
+
PERSON = "Person"
|
|
98
|
+
PLACE = "Place"
|
|
99
|
+
PROFILE = "Profile"
|
|
100
|
+
QUESTION = "Question"
|
|
101
|
+
SERVICE = "Service"
|
|
102
|
+
TOMBSTONE = "Tombstone"
|
|
103
|
+
VIDEO = "Video"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# The sub-list of ActivityStreams types that are also objects in AS 2.0
|
|
107
|
+
ACTIVITY_STREAMS_OBJECTS = [
|
|
108
|
+
ActivityStreamsTypes::ACTIVITY,
|
|
109
|
+
ActivityStreamsTypes::APPLICATION,
|
|
110
|
+
ActivityStreamsTypes::ARTICLE,
|
|
111
|
+
ActivityStreamsTypes::AUDIO,
|
|
112
|
+
ActivityStreamsTypes::COLLECTION,
|
|
113
|
+
ActivityStreamsTypes::COLLECTION_PAGE,
|
|
114
|
+
ActivityStreamsTypes::RELATIONSHIP,
|
|
115
|
+
ActivityStreamsTypes::DOCUMENT,
|
|
116
|
+
ActivityStreamsTypes::EVENT,
|
|
117
|
+
ActivityStreamsTypes::GROUP,
|
|
118
|
+
ActivityStreamsTypes::IMAGE,
|
|
119
|
+
ActivityStreamsTypes::INTRANSITIVE_ACTIVITY,
|
|
120
|
+
ActivityStreamsTypes::NOTE,
|
|
121
|
+
ActivityStreamsTypes::OBJECT,
|
|
122
|
+
ActivityStreamsTypes::ORDERED_COLLECTION,
|
|
123
|
+
ActivityStreamsTypes::ORDERED_COLLECTION_PAGE,
|
|
124
|
+
ActivityStreamsTypes::ORGANIZATION,
|
|
125
|
+
ActivityStreamsTypes::PAGE,
|
|
126
|
+
ActivityStreamsTypes::PERSON,
|
|
127
|
+
ActivityStreamsTypes::PLACE,
|
|
128
|
+
ActivityStreamsTypes::PROFILE,
|
|
129
|
+
ActivityStreamsTypes::QUESTION,
|
|
130
|
+
ActivityStreamsTypes::SERVICE,
|
|
131
|
+
ActivityStreamsTypes::TOMBSTONE,
|
|
132
|
+
ActivityStreamsTypes::VIDEO
|
|
133
|
+
].freeze
|
|
134
|
+
|
|
135
|
+
# A simple wrapper around an ActivityStreams hash object
|
|
136
|
+
#
|
|
137
|
+
# Construct it with a ruby hash that represents an ActivityStreams object, or
|
|
138
|
+
# without to create a fresh, blank object.
|
|
139
|
+
class ActivityStream
|
|
140
|
+
attr_reader :doc, :context
|
|
141
|
+
|
|
142
|
+
# Construct a new ActivityStream object
|
|
143
|
+
#
|
|
144
|
+
# @param raw [Hash] the raw ActivityStreams object, as a hash
|
|
145
|
+
def initialize(raw = nil)
|
|
146
|
+
@doc = raw || {}
|
|
147
|
+
@context = []
|
|
148
|
+
if @doc.key?("@context")
|
|
149
|
+
@context = @doc["@context"]
|
|
150
|
+
@context = [@context] unless @context.is_a?(Array)
|
|
151
|
+
@doc.delete("@context")
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Set the document hash
|
|
156
|
+
#
|
|
157
|
+
# @param doc [Hash] the document hash to set
|
|
158
|
+
def doc=(doc)
|
|
159
|
+
@doc = doc
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Set the context
|
|
163
|
+
#
|
|
164
|
+
# @param context [Array, String] the context to set
|
|
165
|
+
def context=(context)
|
|
166
|
+
@context = context
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Register a namespace in the context of the ActivityStream
|
|
170
|
+
#
|
|
171
|
+
# @param namespace [String, Array] the namespace to register
|
|
172
|
+
def register_namespace(namespace)
|
|
173
|
+
entry = namespace
|
|
174
|
+
if namespace.is_a?(Array)
|
|
175
|
+
url = namespace[1]
|
|
176
|
+
short = namespace[0]
|
|
177
|
+
entry = { short => url }
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
@context << entry unless @context.include?(entry)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Set an arbitrary property on the object. The property name can be one of:
|
|
184
|
+
#
|
|
185
|
+
# * A simple string with the property name
|
|
186
|
+
# * An array of the property name and the full namespace ["name", "http://example.com/ns"]
|
|
187
|
+
# * An array containing the property name and another array of the short name and the full namespace ["name", ["as", "http://example.com/ns"]]
|
|
188
|
+
#
|
|
189
|
+
# @param property [String, Array] the property name
|
|
190
|
+
# @param value [Object] the value to set
|
|
191
|
+
def set_property(property, value)
|
|
192
|
+
prop_name = property
|
|
193
|
+
namespace = nil
|
|
194
|
+
if property.is_a?(Array)
|
|
195
|
+
prop_name = property[0]
|
|
196
|
+
namespace = property[1]
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
@doc[prop_name] = value
|
|
200
|
+
register_namespace(namespace) if namespace
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Get an arbitrary property on the object. The property name can be one of:
|
|
204
|
+
#
|
|
205
|
+
# * A simple string with the property name
|
|
206
|
+
# * An array of the property name and the full namespace ["name", "http://example.com/ns"]
|
|
207
|
+
# * An array containing the property name and another array of the short name and the full namespace ["name", ["as", "http://example.com/ns"]]
|
|
208
|
+
#
|
|
209
|
+
# @param property [String, Array] the property name
|
|
210
|
+
# @return [Object] the value of the property, or nil if it does not exist
|
|
211
|
+
def get_property(property)
|
|
212
|
+
prop_name = property
|
|
213
|
+
namespace = nil
|
|
214
|
+
if property.is_a?(Array)
|
|
215
|
+
prop_name = property[0]
|
|
216
|
+
namespace = property[1]
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
@doc[prop_name]
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Get the activity stream as a JSON-LD object
|
|
223
|
+
#
|
|
224
|
+
# @return [Hash] the JSON-LD representation
|
|
225
|
+
def to_jsonld
|
|
226
|
+
{
|
|
227
|
+
"@context" => @context,
|
|
228
|
+
**@doc
|
|
229
|
+
}
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|