From 8a86fe7bb0785ea69e591fd287430eb5448ac64e Mon Sep 17 00:00:00 2001
From: Andrey Kumanyaev <me@zzet.org>
Date: Sat, 19 Jan 2013 21:06:50 +0400
Subject: [PATCH] Added UserTeam core models (team and m-t-m relationships) and
 updated other models

---
 app/models/concerns/issuable.rb               |  1 +
 app/models/project.rb                         | 39 +++++----
 app/models/user.rb                            | 35 +++++---
 app/models/user_team.rb                       | 87 +++++++++++++++++++
 app/models/user_team_project_relationship.rb  | 24 +++++
 app/models/user_team_user_relationship.rb     | 15 ++++
 app/models/users_project.rb                   |  3 +
 .../20121219183753_create_user_teams.rb       | 11 +++
 ..._create_user_team_project_relationships.rb | 11 +++
 ...453_create_user_team_user_relationships.rb | 12 +++
 db/schema.rb                                  | 25 ++++++
 .../user_team_project_relationship_spec.rb    |  5 ++
 spec/models/user_team_spec.rb                 |  5 ++
 .../user_team_user_relationship_spec.rb       |  5 ++
 14 files changed, 249 insertions(+), 29 deletions(-)
 create mode 100644 app/models/user_team.rb
 create mode 100644 app/models/user_team_project_relationship.rb
 create mode 100644 app/models/user_team_user_relationship.rb
 create mode 100644 db/migrate/20121219183753_create_user_teams.rb
 create mode 100644 db/migrate/20121220064104_create_user_team_project_relationships.rb
 create mode 100644 db/migrate/20121220064453_create_user_team_user_relationships.rb
 create mode 100644 spec/models/user_team_project_relationship_spec.rb
 create mode 100644 spec/models/user_team_spec.rb
 create mode 100644 spec/models/user_team_user_relationship_spec.rb

diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index d1717d3bb..8872cf59a 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -22,6 +22,7 @@ module Issuable
     scope :opened, where(closed: false)
     scope :closed, where(closed: true)
     scope :of_group, ->(group) { where(project_id: group.project_ids) }
+    scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) }
     scope :assigned, ->(u) { where(assignee_id: u.id)}
     scope :recent, order("created_at DESC")
 
diff --git a/app/models/project.rb b/app/models/project.rb
index fa38093b7..fa314d9c5 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -33,28 +33,31 @@ class Project < ActiveRecord::Base
   attr_accessor :error_code
 
   # Relations
-  belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'"
+  belongs_to :creator,      foreign_key: "creator_id", class_name: "User"
+  belongs_to :group,        foreign_key: "namespace_id", conditions: "type = 'Group'"
   belongs_to :namespace
 
-  belongs_to :creator,
-    class_name: "User",
-    foreign_key: "creator_id"
-
-  has_many :users,          through: :users_projects
-  has_many :events,         dependent: :destroy
-  has_many :merge_requests, dependent: :destroy
-  has_many :issues,         dependent: :destroy, order: "closed, created_at DESC"
-  has_many :milestones,     dependent: :destroy
-  has_many :users_projects, dependent: :destroy
-  has_many :notes,          dependent: :destroy
-  has_many :snippets,       dependent: :destroy
-  has_many :deploy_keys,    dependent: :destroy, foreign_key: "project_id", class_name: "Key"
-  has_many :hooks,          dependent: :destroy, class_name: "ProjectHook"
-  has_many :wikis,          dependent: :destroy
-  has_many :protected_branches, dependent: :destroy
   has_one :last_event, class_name: 'Event', order: 'events.created_at DESC', foreign_key: 'project_id'
   has_one :gitlab_ci_service, dependent: :destroy
 
+  has_many :events,             dependent: :destroy
+  has_many :merge_requests,     dependent: :destroy
+  has_many :issues,             dependent: :destroy, order: "closed, created_at DESC"
+  has_many :milestones,         dependent: :destroy
+  has_many :users_projects,     dependent: :destroy
+  has_many :notes,              dependent: :destroy
+  has_many :snippets,           dependent: :destroy
+  has_many :deploy_keys,        dependent: :destroy, class_name: "Key", foreign_key: "project_id"
+  has_many :hooks,              dependent: :destroy, class_name: "ProjectHook"
+  has_many :wikis,              dependent: :destroy
+  has_many :protected_branches, dependent: :destroy
+  has_many :user_team_project_relationships, dependent: :destroy
+
+  has_many :users,          through: :users_projects
+  has_many :user_teams,     through: :user_team_project_relationships
+  has_many :user_team_user_relationships, through: :user_teams
+  has_many :user_teams_members, through: :user_team_user_relationships
+
   delegate :name, to: :owner, allow_nil: true, prefix: true
 
   # Validations
@@ -77,6 +80,8 @@ class Project < ActiveRecord::Base
   # Scopes
   scope :without_user, ->(user)  { where("id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) }
   scope :not_in_group, ->(group) { where("id NOT IN (:ids)", ids: group.project_ids ) }
+  scope :without_team, ->(team) { where("id NOT IN (:ids)", ids: team.projects.map(&:id)) }
+  scope :in_team, ->(team) { where("id IN (:ids)", ids: team.projects.map(&:id)) }
   scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) }
   scope :sorted_by_activity, ->() { order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC") }
   scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
diff --git a/app/models/user.rb b/app/models/user.rb
index 743d7523b..16e07e9ce 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -45,18 +45,27 @@ class User < ActiveRecord::Base
   attr_accessor :force_random_password
 
   # Namespace for personal projects
-  has_one :namespace, class_name: "Namespace", foreign_key: :owner_id, conditions: 'type IS NULL', dependent: :destroy
-  has_many :groups, class_name: "Group", foreign_key: :owner_id
-
-  has_many :keys, dependent: :destroy
-  has_many :users_projects, dependent: :destroy
-  has_many :issues, foreign_key: :author_id, dependent: :destroy
-  has_many :notes, foreign_key: :author_id, dependent: :destroy
-  has_many :merge_requests, foreign_key: :author_id, dependent: :destroy
-  has_many :events, class_name: "Event", foreign_key: :author_id, dependent: :destroy
-  has_many :recent_events, class_name: "Event", foreign_key: :author_id, order: "id DESC"
-  has_many :assigned_issues, class_name: "Issue", foreign_key: :assignee_id, dependent: :destroy
-  has_many :assigned_merge_requests, class_name: "MergeRequest", foreign_key: :assignee_id, dependent: :destroy
+  has_one :namespace,                 dependent: :destroy, foreign_key: :owner_id,    class_name: "Namespace", conditions: 'type IS NULL'
+
+  has_many :keys,                     dependent: :destroy
+  has_many :users_projects,           dependent: :destroy
+  has_many :issues,                   dependent: :destroy, foreign_key: :author_id
+  has_many :notes,                    dependent: :destroy, foreign_key: :author_id
+  has_many :merge_requests,           dependent: :destroy, foreign_key: :author_id
+  has_many :events,                   dependent: :destroy, foreign_key: :author_id,   class_name: "Event"
+  has_many :assigned_issues,          dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue"
+  has_many :assigned_merge_requests,  dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
+
+  has_many :groups,         class_name: "Group", foreign_key: :owner_id
+  has_many :recent_events,  class_name: "Event", foreign_key: :author_id, order: "id DESC"
+
+  has_many :projects,       through: :users_projects
+
+  has_many :user_team_user_relationships, dependent: :destroy
+
+  has_many :user_teams,                      through: :user_team_user_relationships
+  has_many :user_team_project_relationships, through: :user_teams
+  has_many :team_projects,                   through: :user_team_project_relationships
 
   validates :name, presence: true
   validates :bio, length: { within: 0..255 }
@@ -80,6 +89,8 @@ class User < ActiveRecord::Base
   scope :blocked, where(blocked:  true)
   scope :active, where(blocked:  false)
   scope :alphabetically, order('name ASC')
+  scope :in_team, ->(team){ where(id: team.member_ids) }
+  scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
 
   #
   # Class methods
diff --git a/app/models/user_team.rb b/app/models/user_team.rb
new file mode 100644
index 000000000..d402fd22e
--- /dev/null
+++ b/app/models/user_team.rb
@@ -0,0 +1,87 @@
+class UserTeam < ActiveRecord::Base
+  attr_accessible :name, :owner_id, :path
+
+  belongs_to :owner, class_name: User
+
+  has_many :user_team_project_relationships, dependent: :destroy
+  has_many :user_team_user_relationships, dependent: :destroy
+
+  has_many :projects, through: :user_team_project_relationships
+  has_many :members,  through: :user_team_user_relationships, source: :user
+
+  validates :name, presence: true, uniqueness: true
+  validates :owner, presence: true
+  validates :path, uniqueness: true, presence: true, length: { within: 1..255 },
+            format: { with: Gitlab::Regex.path_regex,
+                      message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
+
+  scope :with_member, ->(user){ joins(:user_team_user_relationships).where(user_team_user_relationships: {user_id: user.id}) }
+  scope :created_by, ->(user){ where(owner_id: user) }
+
+  class << self
+    def search query
+      where("name LIKE :query OR path LIKE :query", query: "%#{query}%")
+    end
+
+    def global_id
+      'GLN'
+    end
+
+    def access_roles
+      UsersProject.access_roles
+    end
+  end
+
+  def to_param
+    path
+  end
+
+  def assign_to_projects(projects, access)
+    projects.each do |project|
+      assign_to_project(project, access)
+    end
+  end
+
+  def assign_to_project(project, access)
+    Gitlab::UserTeamManager.assign(self, project, access)
+  end
+
+  def resign_from_project(project)
+    Gitlab::UserTeamManager.resign(self, project)
+  end
+
+  def add_members(users, access, group_admin)
+    users.each do |user|
+      add_member(user, access, group_admin)
+    end
+  end
+
+  def add_member(user, access, group_admin)
+    Gitlab::UserTeamManager.add_member_into_team(self, user, access, group_admin)
+  end
+
+  def remove_member(user)
+    Gitlab::UserTeamManager.remove_member_from_team(self, user)
+  end
+
+  def max_project_access(project)
+    user_team_project_relationships.find_by_project_id(project).greatest_access
+  end
+
+  def human_max_project_access(project)
+    self.class.access_roles.invert[max_project_access(project)]
+  end
+
+  def default_projects_access(member)
+    user_team_user_relationships.find_by_user_id(member).permission
+  end
+
+  def human_default_projects_access(member)
+    self.class.access_roles.invert[default_projects_access(member)]
+  end
+
+  def admin?(member)
+    user_team_user_relationships.with_user(member).first.group_admin?
+  end
+
+end
diff --git a/app/models/user_team_project_relationship.rb b/app/models/user_team_project_relationship.rb
new file mode 100644
index 000000000..4413c492a
--- /dev/null
+++ b/app/models/user_team_project_relationship.rb
@@ -0,0 +1,24 @@
+class UserTeamProjectRelationship < ActiveRecord::Base
+  attr_accessible :greatest_access, :project_id, :user_team_id
+
+  belongs_to :user_team
+  belongs_to :project
+
+  validates :project,   presence: true
+  validates :user_team, presence: true
+  validate :check_greatest_access
+
+  scope :with_project, ->(project){ where(project_id: project.id) }
+
+  private
+
+  def check_greatest_access
+    errors.add(:base, :incorrect_access_code) unless correct_access?
+  end
+
+  def correct_access?
+    return false if greatest_access.blank?
+    return true if UsersProject.access_roles.has_value?(greatest_access)
+    false
+  end
+end
diff --git a/app/models/user_team_user_relationship.rb b/app/models/user_team_user_relationship.rb
new file mode 100644
index 000000000..00d12ebf6
--- /dev/null
+++ b/app/models/user_team_user_relationship.rb
@@ -0,0 +1,15 @@
+class UserTeamUserRelationship < ActiveRecord::Base
+  attr_accessible :group_admin, :permission, :user_id, :user_team_id
+
+  belongs_to :user_team
+  belongs_to :user
+
+  validates :user_team, presence: true
+  validates :user,      presence: true
+
+  scope :with_user, ->(user) { where(user_id: user.id) }
+
+  def user_name
+    user.name
+  end
+end
diff --git a/app/models/users_project.rb b/app/models/users_project.rb
index 791462898..d282b2acb 100644
--- a/app/models/users_project.rb
+++ b/app/models/users_project.rb
@@ -39,7 +39,10 @@ class UsersProject < ActiveRecord::Base
   scope :reporters, where(project_access: REPORTER)
   scope :developers, where(project_access: DEVELOPER)
   scope :masters, where(project_access: MASTER)
+
   scope :in_project, ->(project) { where(project_id: project.id) }
+  scope :in_projects, ->(projects) { where(project_id: projects.map(&:id)) }
+  scope :with_user, ->(user) { where(user_id: user.id) }
 
   class << self
 
diff --git a/db/migrate/20121219183753_create_user_teams.rb b/db/migrate/20121219183753_create_user_teams.rb
new file mode 100644
index 000000000..65c4d0539
--- /dev/null
+++ b/db/migrate/20121219183753_create_user_teams.rb
@@ -0,0 +1,11 @@
+class CreateUserTeams < ActiveRecord::Migration
+  def change
+    create_table :user_teams do |t|
+      t.string :name
+      t.string :path
+      t.integer :owner_id
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/migrate/20121220064104_create_user_team_project_relationships.rb b/db/migrate/20121220064104_create_user_team_project_relationships.rb
new file mode 100644
index 000000000..8eb654c87
--- /dev/null
+++ b/db/migrate/20121220064104_create_user_team_project_relationships.rb
@@ -0,0 +1,11 @@
+class CreateUserTeamProjectRelationships < ActiveRecord::Migration
+  def change
+    create_table :user_team_project_relationships do |t|
+      t.integer :project_id
+      t.integer :user_team_id
+      t.integer :greatest_access
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/migrate/20121220064453_create_user_team_user_relationships.rb b/db/migrate/20121220064453_create_user_team_user_relationships.rb
new file mode 100644
index 000000000..7783b0ae4
--- /dev/null
+++ b/db/migrate/20121220064453_create_user_team_user_relationships.rb
@@ -0,0 +1,12 @@
+class CreateUserTeamUserRelationships < ActiveRecord::Migration
+  def change
+    create_table :user_team_user_relationships do |t|
+      t.integer :user_id
+      t.integer :user_team_id
+      t.boolean :group_admin
+      t.integer :permission
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 4b3a22436..88849872a 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -213,6 +213,31 @@ ActiveRecord::Schema.define(:version => 20130110172407) do
     t.string "name"
   end
 
+  create_table "user_team_project_relationships", :force => true do |t|
+    t.integer  "project_id"
+    t.integer  "user_team_id"
+    t.integer  "greatest_access"
+    t.datetime "created_at",      :null => false
+    t.datetime "updated_at",      :null => false
+  end
+
+  create_table "user_team_user_relationships", :force => true do |t|
+    t.integer  "user_id"
+    t.integer  "user_team_id"
+    t.boolean  "group_admin"
+    t.integer  "permission"
+    t.datetime "created_at",   :null => false
+    t.datetime "updated_at",   :null => false
+  end
+
+  create_table "user_teams", :force => true do |t|
+    t.string   "name"
+    t.string   "path"
+    t.integer  "owner_id"
+    t.datetime "created_at", :null => false
+    t.datetime "updated_at", :null => false
+  end
+
   create_table "users", :force => true do |t|
     t.string   "email",                  :default => "",    :null => false
     t.string   "encrypted_password",     :default => "",    :null => false
diff --git a/spec/models/user_team_project_relationship_spec.rb b/spec/models/user_team_project_relationship_spec.rb
new file mode 100644
index 000000000..81051d599
--- /dev/null
+++ b/spec/models/user_team_project_relationship_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe UserTeamProjectRelationship do
+  pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/user_team_spec.rb b/spec/models/user_team_spec.rb
new file mode 100644
index 000000000..2d1b99db6
--- /dev/null
+++ b/spec/models/user_team_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe UserTeam do
+  pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/user_team_user_relationship_spec.rb b/spec/models/user_team_user_relationship_spec.rb
new file mode 100644
index 000000000..309f1975e
--- /dev/null
+++ b/spec/models/user_team_user_relationship_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe UserTeamUserRelationship do
+  pending "add some examples to (or delete) #{__FILE__}"
+end
-- 
2.30.9