restpack_group_service 0.0.3 → 0.0.5
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 +4 -4
- data/README.md +2 -1
- data/Rakefile +1 -1
- data/config/database.yml +8 -0
- data/db/migrate/20130918110826_create_groups.rb +14 -0
- data/db/migrate/20130918113133_create_invitations.rb +17 -0
- data/db/migrate/20130918113142_create_memberships.rb +13 -0
- data/lib/restpack_group_service.rb +2 -5
- data/lib/restpack_group_service/commands/group/create.rb +28 -0
- data/lib/restpack_group_service/commands/group/get.rb +25 -0
- data/lib/restpack_group_service/commands/group/list.rb +34 -0
- data/lib/restpack_group_service/commands/membership/list.rb +36 -0
- data/lib/restpack_group_service/configuration.rb +12 -0
- data/lib/restpack_group_service/models/group.rb +31 -0
- data/lib/restpack_group_service/models/invitation.rb +99 -0
- data/lib/restpack_group_service/models/membership.rb +11 -0
- data/lib/restpack_group_service/serializers/group.rb +12 -0
- data/lib/restpack_group_service/serializers/membership.rb +12 -0
- data/lib/restpack_group_service/tasks/db.rake +39 -0
- data/lib/restpack_group_service/tasks/tasks.rb +7 -0
- data/lib/restpack_group_service/version.rb +1 -1
- data/restpack_group_service.gemspec +0 -5
- data/spec/commands/group/create_spec.rb +39 -0
- data/spec/commands/group/get_spec.rb +54 -0
- data/spec/commands/group/list_spec.rb +62 -0
- data/spec/commands/membership/list_spec.rb +8 -0
- data/spec/factories/group_factory.rb +9 -0
- data/spec/factories/invitation_factory.rb +7 -0
- data/spec/models/group_spec.rb +27 -0
- data/spec/models/invitiation_spec.rb +123 -0
- data/spec/models/membership_spec.rb +10 -0
- data/spec/spec_helper.rb +22 -14
- metadata +37 -73
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5f59fe4026cc51c3b027b697e09dbadb58659185
|
4
|
+
data.tar.gz: 9014a6a6cfaa115a7a5f10f84f89665c058ebf1b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8d85127110d7bd149d818661fe5f5f213b37b7df1adde720437ec30ce8033f7607193155370a2677ffeef9d3e3c62adde53aae7fb5ee2700a3278d69161bd376
|
7
|
+
data.tar.gz: 7c369ac7537e2121b8daf77f964136405c4a18d199f0567dde1cecbeed647b506423ac07ddb5e01b9b792b308d0990635613d54cbbc71097c1f804f622cd9d8d
|
data/README.md
CHANGED
data/Rakefile
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
require
|
1
|
+
require 'restpack_gem'
|
2
2
|
RestPack::Gem::Tasks.load_service_tasks
|
data/config/database.yml
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
class CreateGroups < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :restpack_groups do |t|
|
4
|
+
t.integer :application_id, :null => false
|
5
|
+
t.integer :account_id
|
6
|
+
t.integer :created_by, :null => false
|
7
|
+
t.string :name, :null => false, :limit => 256
|
8
|
+
t.string :description, :limit => 1024
|
9
|
+
t.boolean :invitation_required, :null => false
|
10
|
+
|
11
|
+
t.timestamps
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class CreateInvitations < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :restpack_invitations do |t|
|
4
|
+
t.integer :application_id, :null => false
|
5
|
+
t.integer :group_id, :null => false
|
6
|
+
t.integer :inviter_id, :null => false
|
7
|
+
t.integer :invitee_id
|
8
|
+
t.integer :status_id, :null => false
|
9
|
+
t.string :email, :limit => 512
|
10
|
+
t.string :access_key, :limit => 128
|
11
|
+
t.datetime :expires_at
|
12
|
+
t.integer :remaining_uses
|
13
|
+
|
14
|
+
t.timestamps
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class CreateMemberships < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :restpack_memberships do |t|
|
4
|
+
t.integer :application_id, :null => false
|
5
|
+
t.integer :account_id
|
6
|
+
t.integer :group_id, :null => false
|
7
|
+
t.integer :user_id, :null => false
|
8
|
+
t.integer :invitation_id
|
9
|
+
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module RestPack::Group::Service::Commands
|
2
|
+
module Group
|
3
|
+
class Create < RestPack::Service::Command
|
4
|
+
required do
|
5
|
+
array :groups do
|
6
|
+
hash do
|
7
|
+
required do
|
8
|
+
integer :application_id
|
9
|
+
integer :created_by
|
10
|
+
string :name
|
11
|
+
end
|
12
|
+
|
13
|
+
optional do
|
14
|
+
integer :account_id
|
15
|
+
string :description
|
16
|
+
boolean :invitation_required
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def execute
|
23
|
+
groups = Models::Group.create!(inputs[:groups])
|
24
|
+
Serializers::Group.serialize(groups)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module RestPack::Group::Service::Commands
|
2
|
+
module Group
|
3
|
+
class Get < RestPack::Service::Command
|
4
|
+
required do
|
5
|
+
integer :id
|
6
|
+
integer :application_id
|
7
|
+
end
|
8
|
+
|
9
|
+
def execute
|
10
|
+
# TODO: GJ: remove the :application_id scope when we can specify custom serializer filters
|
11
|
+
# https://github.com/RestPack/restpack_serializer/issues/42
|
12
|
+
result = Serializers::Group.resource(
|
13
|
+
inputs,
|
14
|
+
Models::Group.where(application_id: inputs[:application_id])
|
15
|
+
)
|
16
|
+
|
17
|
+
if result[:groups].empty?
|
18
|
+
status :not_found
|
19
|
+
else
|
20
|
+
result
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module RestPack::Group::Service::Commands
|
2
|
+
module Group
|
3
|
+
class List < RestPack::Service::Command
|
4
|
+
required do
|
5
|
+
integer :application_id
|
6
|
+
end
|
7
|
+
|
8
|
+
optional do
|
9
|
+
integer :account_id
|
10
|
+
integer :created_by
|
11
|
+
boolean :is_account_group, default: false
|
12
|
+
integer :page
|
13
|
+
integer :page_size
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute
|
17
|
+
# TODO: GJ: remove the scope when we can specify custom serializer filters
|
18
|
+
# https://github.com/RestPack/restpack_serializer/issues/42
|
19
|
+
scope = Models::Group.all
|
20
|
+
scope = scope.where(application_id: application_id)
|
21
|
+
scope = scope.where(account_id: account_id) if account_id
|
22
|
+
scope = scope.where(created_by: created_by) if created_by
|
23
|
+
|
24
|
+
if is_account_group
|
25
|
+
scope = scope.where("account_id IS NOT NULL")
|
26
|
+
else
|
27
|
+
scope = scope.where("account_id IS NULL") unless account_id
|
28
|
+
end
|
29
|
+
|
30
|
+
Serializers::Group.resource(inputs, scope)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module RestPack::Group::Service::Commands
|
2
|
+
module Membership
|
3
|
+
class List < RestPack::Service::Command
|
4
|
+
required do
|
5
|
+
integer :application_id
|
6
|
+
end
|
7
|
+
|
8
|
+
optional do
|
9
|
+
integer :account_id
|
10
|
+
integer :group_id
|
11
|
+
integer :user_id
|
12
|
+
boolean :is_account_group, default: false
|
13
|
+
integer :page
|
14
|
+
integer :page_size
|
15
|
+
end
|
16
|
+
|
17
|
+
def execute
|
18
|
+
# TODO: GJ: remove the scope when we can specify custom serializer filters
|
19
|
+
# https://github.com/RestPack/restpack_serializer/issues/42
|
20
|
+
scope = Models::Membership.all
|
21
|
+
scope = scope.where(application_id: application_id)
|
22
|
+
scope = scope.where(account_id: account_id) if account_id
|
23
|
+
scope = scope.where(group_id: group_id) if group_id
|
24
|
+
scope = scope.where(user_id: user_id) if user_id
|
25
|
+
|
26
|
+
if is_account_group
|
27
|
+
scope = scope.where("account_id IS NOT NULL")
|
28
|
+
else
|
29
|
+
scope = scope.where("account_id IS NULL") unless account_id
|
30
|
+
end
|
31
|
+
|
32
|
+
Serializers::Membership.resource(inputs, scope)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module RestPack::Group::Service::Models
|
2
|
+
class Group < ActiveRecord::Base
|
3
|
+
self.table_name = :restpack_groups
|
4
|
+
|
5
|
+
attr_accessible :application_id, :account_id, :created_by, :name, :description, :invitation_required
|
6
|
+
validates_presence_of :application_id, :created_by, :name
|
7
|
+
|
8
|
+
validates :name, :length => { :maximum => 256 }
|
9
|
+
validates :description, :length => { :maximum => 1024 }
|
10
|
+
|
11
|
+
has_many :memberships
|
12
|
+
has_many :invitations
|
13
|
+
|
14
|
+
after_initialize :set_defaults
|
15
|
+
after_create :create_default_member
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def set_defaults
|
20
|
+
self.invitation_required ||= false
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_default_member
|
24
|
+
self.memberships << Membership.new(
|
25
|
+
user_id: self.created_by,
|
26
|
+
application_id: self.application_id,
|
27
|
+
account_id: self.account_id
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module RestPack::Group::Service::Models
|
2
|
+
class Invitation < ActiveRecord::Base
|
3
|
+
self.table_name = :restpack_invitations
|
4
|
+
|
5
|
+
STATUS = { pending: 0, available: 1, accepted: 2, cancelled: 3, expired: 4 }
|
6
|
+
attr_accessor :access_key_length, :status
|
7
|
+
attr_accessible :access_key_length, :application_id, :email, :expires_at, :group_id, :invitee_id, :inviter_id, :remaining_uses, :status
|
8
|
+
|
9
|
+
validates_presence_of :application_id, :group_id, :inviter_id, :status_id
|
10
|
+
validates :email, :length => { maximum: 512 }
|
11
|
+
validates :access_key, :length => { maximum: 128 }
|
12
|
+
|
13
|
+
belongs_to :group
|
14
|
+
has_many :memberships
|
15
|
+
|
16
|
+
scope :available, -> { where(:status_id => STATUS[:available]) }
|
17
|
+
|
18
|
+
def self.by_application_id(application_id)
|
19
|
+
self.where 'application_id = ?', application_id
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.by_access_key(key)
|
23
|
+
self.where 'access_key = ?', key
|
24
|
+
end
|
25
|
+
|
26
|
+
after_initialize :set_defaults
|
27
|
+
before_create :generate_access_key
|
28
|
+
|
29
|
+
def status
|
30
|
+
STATUS.key(read_attribute(:status_id))
|
31
|
+
end
|
32
|
+
|
33
|
+
def status=(status)
|
34
|
+
write_attribute(:status_id, STATUS[status])
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.accept(application_id, user_id, access_key) #TODO: GJ: skip if already a member
|
38
|
+
invitation = self.available.by_application_id(application_id).by_access_key(access_key).first
|
39
|
+
|
40
|
+
raise "Invalid invitation" unless invitation
|
41
|
+
|
42
|
+
invitation.accept(user_id)
|
43
|
+
end
|
44
|
+
|
45
|
+
def accept(user_id)
|
46
|
+
validate! user_id
|
47
|
+
|
48
|
+
transaction do
|
49
|
+
membership = Membership.new application_id: application_id, group_id: group_id, user_id: user_id
|
50
|
+
self.memberships << membership
|
51
|
+
self.use!
|
52
|
+
|
53
|
+
membership
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.access_key_unique?(application_id, access_key)
|
58
|
+
self.by_application_id(application_id).by_access_key(access_key).empty?
|
59
|
+
end
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
def use!
|
64
|
+
if single_use?
|
65
|
+
self.status = :accepted
|
66
|
+
else
|
67
|
+
self.remaining_uses -= 1
|
68
|
+
end
|
69
|
+
self.save!
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def validate!(user_id)
|
75
|
+
raise "Invitation is #{status}" unless status == :available
|
76
|
+
raise "Invalid invitee" unless invitee_id.nil? || invitee_id == user_id.to_i
|
77
|
+
raise "Invitation has expired" unless remaining_uses.nil? || remaining_uses > 0
|
78
|
+
raise "Invitation has expired" unless expires_at.nil? || expires_at > Time.now
|
79
|
+
end
|
80
|
+
|
81
|
+
def single_use?
|
82
|
+
remaining_uses.nil? || remaining_uses <= 1
|
83
|
+
end
|
84
|
+
|
85
|
+
def set_defaults
|
86
|
+
self.access_key_length ||= 16
|
87
|
+
self.status ||= :available
|
88
|
+
end
|
89
|
+
|
90
|
+
def generate_access_key
|
91
|
+
length = access_key_length.to_i
|
92
|
+
|
93
|
+
loop do
|
94
|
+
self.access_key = SecureRandom.urlsafe_base64(length).first(length)
|
95
|
+
break if Invitation.access_key_unique?(application_id, access_key)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module RestPack::Group::Service::Models
|
2
|
+
class Membership < ActiveRecord::Base
|
3
|
+
self.table_name = :restpack_memberships
|
4
|
+
|
5
|
+
attr_accessible :application_id, :account_id, :group_id, :invitation_id, :user_id
|
6
|
+
validates_presence_of :application_id, :group_id, :user_id
|
7
|
+
|
8
|
+
belongs_to :group
|
9
|
+
belongs_to :invitation
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module RestPack::Group::Service::Serializers
|
2
|
+
class Group
|
3
|
+
include RestPack::Serializer
|
4
|
+
|
5
|
+
self.model_class = Models::Group
|
6
|
+
self.key = :groups
|
7
|
+
|
8
|
+
attributes :id, :application_id, :account_id, :created_by, :name,
|
9
|
+
:description, :invitation_required, :href
|
10
|
+
can_include :memberships, :invitations
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module RestPack::Group::Service::Serializers
|
2
|
+
class Membership
|
3
|
+
include RestPack::Serializer
|
4
|
+
|
5
|
+
self.model_class = Models::Membership
|
6
|
+
self.key = :memberships
|
7
|
+
|
8
|
+
attributes :id, :application_id, :account_id, :group_id, :user_id,
|
9
|
+
:invitation_id, :href
|
10
|
+
can_include :groups, :invitations
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
namespace :restpack do
|
2
|
+
desc "Run any outstanding RestPack migrations"
|
3
|
+
task :migrate do
|
4
|
+
Rake::Task["restpack:group:migrate"].invoke
|
5
|
+
end
|
6
|
+
|
7
|
+
desc "List RestPack configuration"
|
8
|
+
task :configuration do
|
9
|
+
Rake::Task["restpack:group:configuration"].invoke
|
10
|
+
end
|
11
|
+
|
12
|
+
namespace :group do
|
13
|
+
desc "Run any outstanding RestPack::Core migrations"
|
14
|
+
task :migrate => ["connection"] do
|
15
|
+
source_migrations_path = File.dirname(__FILE__) + "/../../../db/migrate"
|
16
|
+
target_migrations_path = "db/migrate"
|
17
|
+
|
18
|
+
ActiveRecord::Migration.verbose = true
|
19
|
+
ActiveRecord::Migrator.migrate(source_migrations_path)
|
20
|
+
|
21
|
+
if File.directory?(target_migrations_path)
|
22
|
+
FileUtils.cp_r(Dir["#{source_migrations_path}/*"], target_migrations_path)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
task :connection do
|
27
|
+
config = YAML.load(IO.read('config/database.yml'))
|
28
|
+
environment = ENV['RAILS_ENV'] || ENV['DB'] || 'development'
|
29
|
+
ActiveRecord::Base.establish_connection config[environment]
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "List RestPack::Group::Service configuration"
|
33
|
+
task :configuration do
|
34
|
+
p "RestPack::Group::Service Configuration"
|
35
|
+
p "--------------------------------"
|
36
|
+
p "TODO"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -19,11 +19,6 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_dependency "restpack_service"
|
22
|
-
spec.add_dependency "restpack_serializer"
|
23
|
-
spec.add_dependency "restpack_gem"
|
24
|
-
spec.add_dependency "sinatra", "~> 1.4.3"
|
25
|
-
spec.add_dependency "pg", "~> 0.16"
|
26
|
-
spec.add_dependency "require_all", "~> 1.3.0"
|
27
22
|
|
28
23
|
spec.add_development_dependency "bundler", "~> 1.3"
|
29
24
|
spec.add_development_dependency "database_cleaner", "~> 1.0.1"
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
|
3
|
+
describe Commands::Group::Create do
|
4
|
+
#TODO: GJ: validate array
|
5
|
+
# is_required :application_id, :created_by, :name
|
6
|
+
# is_optional :account_id, :description, :invitation_required
|
7
|
+
|
8
|
+
context 'creating a group' do
|
9
|
+
let(:response) { subject.class.run(params) }
|
10
|
+
|
11
|
+
context 'with valid params' do
|
12
|
+
let(:group) { {
|
13
|
+
application_id: 123,
|
14
|
+
created_by: 234,
|
15
|
+
name: 'My New Group',
|
16
|
+
account_id: 345,
|
17
|
+
description: 'this is the description',
|
18
|
+
invitation_required: true
|
19
|
+
} }
|
20
|
+
let(:params) { {
|
21
|
+
groups: [group]
|
22
|
+
} }
|
23
|
+
|
24
|
+
it 'returns the newly created group' do
|
25
|
+
response.success?.should == true
|
26
|
+
|
27
|
+
groups = response.result[:groups]
|
28
|
+
groups.length.should == 1
|
29
|
+
|
30
|
+
groups.first[:application_id].should == 123
|
31
|
+
groups.first[:created_by].should == 234
|
32
|
+
groups.first[:name].should == "My New Group"
|
33
|
+
groups.first[:account_id].should == 345
|
34
|
+
groups.first[:description].should == 'this is the description'
|
35
|
+
groups.first[:invitation_required].should == true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
|
3
|
+
describe Commands::Group::Get do
|
4
|
+
is_required :id, :application_id
|
5
|
+
|
6
|
+
let(:response) { subject.class.run(params) }
|
7
|
+
let(:params) { {} }
|
8
|
+
|
9
|
+
before do
|
10
|
+
@group = create(:group)
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'with valid params' do
|
14
|
+
let(:params) { {
|
15
|
+
id: @group.id,
|
16
|
+
application_id: @group.application_id
|
17
|
+
} }
|
18
|
+
|
19
|
+
it 'is valid' do
|
20
|
+
response.success?.should == true
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'return the group' do
|
24
|
+
response.result[:groups].length.should == 1
|
25
|
+
response.result[:groups].first[:id].should == @group.id.to_s
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'with invalid :id' do
|
30
|
+
let(:params) { {
|
31
|
+
id: 142857,
|
32
|
+
application_id: @group.application_id
|
33
|
+
} }
|
34
|
+
|
35
|
+
it 'is :not_found' do
|
36
|
+
response.success?.should == false
|
37
|
+
response.result.should == {}
|
38
|
+
response.status.should == :not_found
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'with invalid :application_id' do
|
43
|
+
let(:params) { {
|
44
|
+
id: @group.id,
|
45
|
+
application_id: 142857
|
46
|
+
}}
|
47
|
+
|
48
|
+
it 'is :not_found' do
|
49
|
+
response.success?.should == false
|
50
|
+
response.result.should == {}
|
51
|
+
response.status.should == :not_found
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
|
3
|
+
describe Commands::Group::List do
|
4
|
+
is_required :application_id
|
5
|
+
is_optional :account_id, :created_by, :is_account_group, :page, :page_size
|
6
|
+
|
7
|
+
before do
|
8
|
+
@groups = create_list(:group, 5)
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'listing groups' do
|
12
|
+
let(:response) { subject.class.run(params) }
|
13
|
+
|
14
|
+
context ':application_id' do
|
15
|
+
before { create_list(:group, 3, application_id: 123) }
|
16
|
+
|
17
|
+
context 'valid' do
|
18
|
+
let(:params) { { application_id: 123 } }
|
19
|
+
it 'returns groups' do
|
20
|
+
response.result[:meta][:groups][:count].should == 3
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'invalid' do
|
25
|
+
let(:params) { { application_id: 999999 } }
|
26
|
+
it 'returns no groups' do
|
27
|
+
response.result[:meta][:groups][:count].should == 0
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
pending ':application_id is required'
|
32
|
+
end
|
33
|
+
|
34
|
+
context ':account_id' do
|
35
|
+
before do
|
36
|
+
create_list(:group, 2, application_id: 123)
|
37
|
+
create_list(:group, 2, application_id: 123, account_id: 345)
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'valid' do
|
41
|
+
let(:params) { { application_id: 123, account_id: 345 } }
|
42
|
+
it 'returns groups' do
|
43
|
+
response.result[:meta][:groups][:count].should == 2
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'invalid' do
|
48
|
+
let(:params) { { application_id: 123, account_id: 999999 } }
|
49
|
+
it 'returns no groups' do
|
50
|
+
response.result[:meta][:groups][:count].should == 0
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context ':is_account_group' do
|
55
|
+
let(:params) { { application_id: 123, is_account_group: true } }
|
56
|
+
it 'returns groups that have an account' do
|
57
|
+
response.result[:meta][:groups][:count].should == 2
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Models::Group do
|
4
|
+
it { should validate_presence_of(:application_id) }
|
5
|
+
it { should validate_presence_of(:created_by) }
|
6
|
+
it { should validate_presence_of(:name) }
|
7
|
+
|
8
|
+
it { should ensure_length_of(:name).is_at_most(256) }
|
9
|
+
it { should ensure_length_of(:description).is_at_most(1024) }
|
10
|
+
|
11
|
+
it { should have_many(:memberships) }
|
12
|
+
it { should have_many(:invitations) }
|
13
|
+
|
14
|
+
context "when creating" do
|
15
|
+
context "a valid group" do
|
16
|
+
before do
|
17
|
+
@group = create(:group, account_id: 999)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should create a default member" do
|
21
|
+
@group.memberships.count.should == 1
|
22
|
+
@group.memberships.first.user_id.should == @group.created_by
|
23
|
+
@group.memberships.first.account_id.should == @group.account_id
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Models::Invitation do
|
4
|
+
it { should validate_presence_of(:application_id) }
|
5
|
+
it { should validate_presence_of(:group_id) }
|
6
|
+
it { should validate_presence_of(:inviter_id) }
|
7
|
+
it { should validate_presence_of(:status_id) }
|
8
|
+
|
9
|
+
it { should ensure_length_of(:email).is_at_most(512) }
|
10
|
+
it { should ensure_length_of(:access_key).is_at_most(128) }
|
11
|
+
|
12
|
+
it { should_not allow_mass_assignment_of(:access_key) }
|
13
|
+
|
14
|
+
it { should belong_to(:group) }
|
15
|
+
it { should have_many(:memberships) }
|
16
|
+
|
17
|
+
Membership = Models::Membership
|
18
|
+
Invitation = Models::Invitation
|
19
|
+
|
20
|
+
context "when accepting" do
|
21
|
+
context "a valid single-use user invitation" do
|
22
|
+
before do
|
23
|
+
@invitation = create(:invitation, invitee_id: 142857)
|
24
|
+
|
25
|
+
Membership.count.should == 1
|
26
|
+
@membership = Invitation.accept(@invitation.application_id, @invitation.invitee_id, @invitation.access_key)
|
27
|
+
Membership.count.should == 2
|
28
|
+
end
|
29
|
+
|
30
|
+
it "creates a new member for the group" do
|
31
|
+
@membership.user_id.should == 142857
|
32
|
+
@membership.application_id.should == @invitation.application_id
|
33
|
+
@membership.group_id.should == @invitation.group_id
|
34
|
+
@membership.invitation_id.should == @invitation.id
|
35
|
+
end
|
36
|
+
|
37
|
+
it "marks the invitation as accepted" do
|
38
|
+
@invitation.reload.status.should == :accepted
|
39
|
+
end
|
40
|
+
|
41
|
+
it "can only be used once" do
|
42
|
+
expect do
|
43
|
+
Invitation.accept(@invitation.application_id, @invitation.invitee_id, @invitation.access_key)
|
44
|
+
end.to raise_error('Invalid invitation')
|
45
|
+
end
|
46
|
+
|
47
|
+
it "can only be used by the invitee" do
|
48
|
+
@invitation = create(:invitation, invitee_id: 142857)
|
49
|
+
expect do
|
50
|
+
@membership = Invitation.accept(@invitation.application_id, 999, @invitation.access_key)
|
51
|
+
end.to raise_error('Invalid invitee')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "a valid multi-use invitation" do
|
56
|
+
before do
|
57
|
+
@invitation = create(:invitation, remaining_uses: 3)
|
58
|
+
Membership.count.should == 1
|
59
|
+
end
|
60
|
+
|
61
|
+
it "can be accepted by a user" do
|
62
|
+
@membership = Invitation.accept(@invitation.application_id, 1234, @invitation.access_key)
|
63
|
+
Membership.count.should == 2
|
64
|
+
@membership.user_id.should == 1234
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should decrement the remaining_uses by 1" do
|
68
|
+
@invitation.remaining_uses.should == 3
|
69
|
+
Invitation.accept(@invitation.application_id, 1234, @invitation.access_key)
|
70
|
+
@invitation.reload.remaining_uses.should == 2
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "a multi-use invitation with no remaining uses" do
|
75
|
+
before do
|
76
|
+
@invitation = create(:invitation, remaining_uses: 0)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "can't be used" do
|
80
|
+
expect do
|
81
|
+
Invitation.accept(@invitation.application_id, 1234, @invitation.access_key)
|
82
|
+
end.to raise_error('Invitation has expired')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context "an expired invitation" do
|
87
|
+
before do
|
88
|
+
@invitation = create(:invitation, invitee_id: 142857, expires_at: 1.second.ago)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "can't be used" do
|
92
|
+
expect do
|
93
|
+
Invitation.accept(@invitation.application_id, 142857, @invitation.access_key)
|
94
|
+
end.to raise_error('Invitation has expired')
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context "access_key" do
|
100
|
+
it "defaults to a length of 16" do
|
101
|
+
invitation = create(:invitation)
|
102
|
+
invitation.access_key.length.should == 16
|
103
|
+
end
|
104
|
+
it "allows the access_key length to be set" do
|
105
|
+
invitation = create(:invitation, access_key_length: 5)
|
106
|
+
invitation.access_key.length.should == 5
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context "status" do
|
111
|
+
it "defaults to :available" do
|
112
|
+
invitation = create(:invitation)
|
113
|
+
invitation.status.should == :available
|
114
|
+
end
|
115
|
+
|
116
|
+
[:pending, :available, :accepted, :cancelled, :expired].each do |status|
|
117
|
+
it "can be :#{status}" do
|
118
|
+
invitation = create(:invitation, status: status)
|
119
|
+
invitation.status.should == status
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Models::Membership do
|
4
|
+
it { should validate_presence_of(:application_id) }
|
5
|
+
it { should validate_presence_of(:group_id) }
|
6
|
+
it { should validate_presence_of(:user_id) }
|
7
|
+
|
8
|
+
it { should belong_to(:group) }
|
9
|
+
it { should belong_to(:invitation) }
|
10
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,17 +1,25 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
require 'restpack_service/support/spec_helper'
|
2
|
+
require 'restpack_group_service'
|
3
|
+
|
4
|
+
config = YAML.load_file('./config/database.yml')
|
5
|
+
ActiveRecord::Base.establish_connection(ENV['DATABASE_URL'] || config['test'])
|
6
|
+
|
7
|
+
migrations_path = File.dirname(__FILE__) + "/../db/migrate"
|
8
|
+
migrator = ActiveRecord::Migrator.new(:up, migrations_path)
|
9
|
+
migrator.migrate
|
10
|
+
|
11
|
+
FactoryGirl.find_definitions
|
12
|
+
|
13
|
+
DatabaseCleaner.strategy = :transaction
|
14
|
+
|
7
15
|
RSpec.configure do |config|
|
8
|
-
config.
|
9
|
-
|
10
|
-
config.
|
16
|
+
config.include FactoryGirl::Syntax::Methods
|
17
|
+
|
18
|
+
config.before(:each) do
|
19
|
+
DatabaseCleaner.start
|
20
|
+
end
|
11
21
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
# --seed 1234
|
16
|
-
config.order = 'random'
|
22
|
+
config.after(:each) do
|
23
|
+
DatabaseCleaner.clean
|
24
|
+
end
|
17
25
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: restpack_group_service
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gavin Joyce
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-09-
|
11
|
+
date: 2013-09-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: restpack_service
|
@@ -24,76 +24,6 @@ dependencies:
|
|
24
24
|
- - '>='
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: restpack_serializer
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - '>='
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - '>='
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: restpack_gem
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - '>='
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '0'
|
48
|
-
type: :runtime
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - '>='
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: sinatra
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ~>
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: 1.4.3
|
62
|
-
type: :runtime
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ~>
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: 1.4.3
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: pg
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - ~>
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0.16'
|
76
|
-
type: :runtime
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - ~>
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0.16'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: require_all
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - ~>
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: 1.3.0
|
90
|
-
type: :runtime
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - ~>
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: 1.3.0
|
97
27
|
- !ruby/object:Gem::Dependency
|
98
28
|
name: bundler
|
99
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -206,9 +136,34 @@ files:
|
|
206
136
|
- LICENSE.txt
|
207
137
|
- README.md
|
208
138
|
- Rakefile
|
139
|
+
- config/database.yml
|
140
|
+
- db/migrate/20130918110826_create_groups.rb
|
141
|
+
- db/migrate/20130918113133_create_invitations.rb
|
142
|
+
- db/migrate/20130918113142_create_memberships.rb
|
209
143
|
- lib/restpack_group_service.rb
|
144
|
+
- lib/restpack_group_service/commands/group/create.rb
|
145
|
+
- lib/restpack_group_service/commands/group/get.rb
|
146
|
+
- lib/restpack_group_service/commands/group/list.rb
|
147
|
+
- lib/restpack_group_service/commands/membership/list.rb
|
148
|
+
- lib/restpack_group_service/configuration.rb
|
149
|
+
- lib/restpack_group_service/models/group.rb
|
150
|
+
- lib/restpack_group_service/models/invitation.rb
|
151
|
+
- lib/restpack_group_service/models/membership.rb
|
152
|
+
- lib/restpack_group_service/serializers/group.rb
|
153
|
+
- lib/restpack_group_service/serializers/membership.rb
|
154
|
+
- lib/restpack_group_service/tasks/db.rake
|
155
|
+
- lib/restpack_group_service/tasks/tasks.rb
|
210
156
|
- lib/restpack_group_service/version.rb
|
211
157
|
- restpack_group_service.gemspec
|
158
|
+
- spec/commands/group/create_spec.rb
|
159
|
+
- spec/commands/group/get_spec.rb
|
160
|
+
- spec/commands/group/list_spec.rb
|
161
|
+
- spec/commands/membership/list_spec.rb
|
162
|
+
- spec/factories/group_factory.rb
|
163
|
+
- spec/factories/invitation_factory.rb
|
164
|
+
- spec/models/group_spec.rb
|
165
|
+
- spec/models/invitiation_spec.rb
|
166
|
+
- spec/models/membership_spec.rb
|
212
167
|
- spec/spec_helper.rb
|
213
168
|
homepage: https://github.com/RestPack
|
214
169
|
licenses:
|
@@ -230,9 +185,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
230
185
|
version: '0'
|
231
186
|
requirements: []
|
232
187
|
rubyforge_project:
|
233
|
-
rubygems_version: 2.0.
|
188
|
+
rubygems_version: 2.0.5
|
234
189
|
signing_key:
|
235
190
|
specification_version: 4
|
236
191
|
summary: Groups, Members and Invitations
|
237
192
|
test_files:
|
193
|
+
- spec/commands/group/create_spec.rb
|
194
|
+
- spec/commands/group/get_spec.rb
|
195
|
+
- spec/commands/group/list_spec.rb
|
196
|
+
- spec/commands/membership/list_spec.rb
|
197
|
+
- spec/factories/group_factory.rb
|
198
|
+
- spec/factories/invitation_factory.rb
|
199
|
+
- spec/models/group_spec.rb
|
200
|
+
- spec/models/invitiation_spec.rb
|
201
|
+
- spec/models/membership_spec.rb
|
238
202
|
- spec/spec_helper.rb
|