diff --git a/app/controllers/personal_repositories_controller.rb b/app/controllers/personal_repositories_controller.rb new file mode 100644 index 000000000..25f9b8f54 --- /dev/null +++ b/app/controllers/personal_repositories_controller.rb @@ -0,0 +1,67 @@ +class PersonalRepositoriesController < ApplicationController + before_filter :authenticate_user! + before_filter :find_repository#, :only => [:show, :destroy, :add_project, :remove_project, :make_private, :settings] + + def show + if params[:query] + @projects = @repository.projects.recent.by_name(params[:query]).paginate :page => params[:project_page], :per_page => 30 + else + @projects = @repository.projects.recent.paginate :page => params[:project_page], :per_page => 30 + end + end + + #TODO: Add git repo move into private repos path. + def change_visibility + @repository.change_visibility + + redirect_to settings_personal_repository_path(@repository) + end + + def settings + if @repository.hidden? + @urmpi_command = "urpmi -add http://login@password:#{ request.host }/privates/#{ @repository.platform.name }/main/" + else + @urmpi_command = "urpmi -add http://#{ request.host }/downloads/#{ @repository.platform.name }/main/" + end + end + + def add_project + if params[:project_id] + @project = Project.find(params[:project_id]) + params[:project_id] = nil + unless @repository.projects.include? @project + @repository.projects << @project + flash[:notice] = t('flash.repository.project_added') + redirect_to platform_repository_path(@repository.platform, @repository) + else + flash[:error] = t('flash.repository.project_not_added') + redirect_to url_for(:action => :add_project) + end + else + @projects = Project.addable_to_repository(@repository.id).paginate(:page => params[:project_page]) + render 'projects_list' + end + end + + def remove_project + if params[:project_id] + @project = Project.find(params[:project_id]) + params[:project_id] = nil + if @repository.projects.include? @project + @repository.projects.delete @project + flash[:notice] = t('flash.repository.project_removed') + redirect_to platform_repository_path(@repository.platform, @repository) + else + redirect_to url_for(:action => :remove_project) + end + else + redirect_to platform_repository_path(@repository.platform, @repository) + end + end + + protected + + def find_repository + @repository = Repository.find(params[:id]) + end +end diff --git a/app/controllers/private_users_controller.rb b/app/controllers/private_users_controller.rb index 02b6e2339..546b8843a 100644 --- a/app/controllers/private_users_controller.rb +++ b/app/controllers/private_users_controller.rb @@ -7,7 +7,7 @@ class PrivateUsersController < ApplicationController end def create - pair = PrivateUser.generate_pair(params[:platform_id]) + pair = PrivateUser.generate_pair(params[:platform_id], current_user.id) redirect_to platform_private_users_path(params[:platform_id]), :notice => "Логин: #{ pair[:login] } Пароль: #{ pair[:pass] }" end diff --git a/app/controllers/privates_controller.rb b/app/controllers/privates_controller.rb index 9b24d42cb..ca82b2f6c 100644 --- a/app/controllers/privates_controller.rb +++ b/app/controllers/privates_controller.rb @@ -7,7 +7,7 @@ class PrivatesController < ApplicationController before_filter :authenticate def show - file_name = "#{APP_CONFIG['private_repo_path']}/#{params[:platform_name]}/#{params[:file_path]}" + file_name = "#{APP_CONFIG['root_path']}/platforms/#{params[:platform_name]}/repository/#{params[:file_path]}" if File.directory?(file_name) || !File.exists?(file_name) render :file => "#{Rails.root}/public/404.html", :layout => false, :status => 404 diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index dad3a6e0e..275a685bd 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -58,7 +58,7 @@ class RepositoriesController < ApplicationController redirect_to url_for(:action => :add_project) end else - @projects = (Project.all - @repository.projects).paginate(:page => params[:project_page]) + @projects = Project.addable_to_repository(@repository.id).paginate(:page => params[:project_page]) render 'projects_list' end end diff --git a/app/helpers/personal_repositories_helper.rb b/app/helpers/personal_repositories_helper.rb new file mode 100644 index 000000000..db846e4c5 --- /dev/null +++ b/app/helpers/personal_repositories_helper.rb @@ -0,0 +1,2 @@ +module PersonalRepositoriesHelper +end diff --git a/app/models/group.rb b/app/models/group.rb index 842d68a92..1c832b81c 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -6,6 +6,10 @@ class Group < ActiveRecord::Base validates :name, :uname, :owner_id, :presence => true validates :name, :uname, :uniqueness => true validates :uname, :format => { :with => /^[a-zA-Z0-9_]+$/ }, :allow_nil => false, :allow_blank => false + #TODO: Replace this simple cross-table uniq validation by more progressive analog + validate lambda { + errors.add(:uname, I18n.t('flash.group.user_uname_exists')) if User.exists? :uname => uname + } belongs_to :global_role, :class_name => 'Role' @@ -20,6 +24,8 @@ class Group < ActiveRecord::Base has_many :platforms, :through => :targets, :source => :target, :source_type => 'Platform', :autosave => true has_many :repositories, :through => :targets, :source => :target, :source_type => 'Repository', :autosave => true + include PersonalRepository + before_save :create_dir after_destroy :remove_dir diff --git a/app/models/personal_repository.rb b/app/models/personal_repository.rb new file mode 100644 index 000000000..54e0da32d --- /dev/null +++ b/app/models/personal_repository.rb @@ -0,0 +1,34 @@ +module PersonalRepository + extend ActiveSupport::Concern + + included do + after_create :create_personal_repository + end + + module InstanceMethods + def create_personal_repository + pl = platforms.build + pl.owner = self + pl.name = "#{self.uname}_personal" + pl.unixname = "#{self.uname}_personal" + pl.platform_type = 'personal' + pl.save + + rep = pl.repositories.build + rep.owner = pl.owner + rep.name = 'main' + rep.unixname = 'main' + rep.visibility = 'open' + rep.save + end + + def personal_platform + platforms.personal.first + end + + def personal_repository + personal_platform.repositories.first + end + end + +end diff --git a/app/models/platform.rb b/app/models/platform.rb index bd38cd30e..c9f11dc9d 100644 --- a/app/models/platform.rb +++ b/app/models/platform.rb @@ -17,7 +17,7 @@ class Platform < ActiveRecord::Base validates :unixname, :uniqueness => true, :presence => true, :format => { :with => /^[a-zA-Z0-9_]+$/ }, :allow_nil => false, :allow_blank => false #after_create :make_owner_rel - before_save :create_directory +# before_save :create_directory before_save :make_owner_rel after_destroy :remove_directory # before_create :xml_rpc_create @@ -25,9 +25,10 @@ class Platform < ActiveRecord::Base # before_update :check_freezing scope :by_visibilities, lambda {|v| {:conditions => ['visibility in (?)', v.join(',')]}} + scope :main, where(:platform_type => 'main') + scope :personal, where(:platform_type => 'personal') attr_accessible :visibility - scope :main, where(:platform_type => 'main') def path build_path(unixname) diff --git a/app/models/private_user.rb b/app/models/private_user.rb index dccdc96db..d0265e454 100644 --- a/app/models/private_user.rb +++ b/app/models/private_user.rb @@ -1,18 +1,26 @@ class PrivateUser < ActiveRecord::Base require 'digest/sha2' require 'active_support/secure_random' + + belongs_to :platform + belongs_to :user validate :login, :uniqueness => true class << self - def generate_pair(platform_id) + def can_generate_more?(user_id) + !PrivateUser.exists?(:user_id => user_id) + end + + def generate_pair(platform_id, user_id) login = "login_#{ActiveSupport::SecureRandom.hex(16)}" pass = "pass_#{ActiveSupport::SecureRandom.hex(16)}" PrivateUser.create( :login => login, :password => Digest::SHA2.new.hexdigest(pass), - :platform_id => platform_id + :platform_id => platform_id, + :user_id => user_id ) {:login => login, :pass => pass} diff --git a/app/models/project.rb b/app/models/project.rb index c79d7f8d8..696ff8ea9 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -25,14 +25,23 @@ class Project < ActiveRecord::Base scope :by_name, lambda { |name| {:conditions => ['name like ?', '%' + name + '%']} } scope :by_visibilities, lambda {|v| {:conditions => ['visibility in (?)', v.join(',')]}} + + scope :addable_to_repository, lambda { |repository_id| where("projects.id NOT IN (SELECT project_to_repositories.project_id FROM project_to_repositories WHERE (project_to_repositories.repository_id != #{ repository_id }))") } before_save :create_directory#, :create_git_repo before_save :make_owner_rel after_destroy :remove_directory + after_create :attach_to_personal_repository + # before_create :xml_rpc_create # before_destroy :xml_rpc_destroy attr_accessible :visibility + + def attach_to_personal_repository + repositories << self.owner.personal_repository if !repositories.exists?(:id => self.owner.personal_repository) + end + def project_versions self.git_repository.tags end diff --git a/app/models/repository.rb b/app/models/repository.rb index 3cecd90ab..83e78cd04 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1,5 +1,7 @@ class Repository < ActiveRecord::Base + DOWNLOADS_PATH = RAILS_ROOT + '/public/downloads' + VISIBILITIES = ['open', 'hidden'] relationable :as => :target @@ -20,19 +22,27 @@ class Repository < ActiveRecord::Base scope :recent, order("name ASC") scope :by_visibilities, lambda {|v| {:conditions => ['visibility in (?)', v.join(',')]}} - before_save :create_directory + #before_save :create_directory before_save :make_owner_rel - after_destroy :remove_directory + #after_destroy :remove_directory # before_create :xml_rpc_create # before_destroy :xml_rpc_destroy - attr_accessible :visibility + after_create lambda { + add_downloads_symlink unless self.hidden? + } + attr_accessible :visibility + def path build_path(unixname) end + def hidden? + self.visibility == 'hidden' + end + def clone r = Repository.new r.name = name @@ -40,6 +50,16 @@ class Repository < ActiveRecord::Base r.projects = projects.map(&:clone) return r end + + def change_visibility + if !self.hidden? + self.update_attribute(:visibility, 'hidden') + remove_downloads_symlink + else + self.update_attribute(:visibility, 'open') + add_downloads_symlink + end + end protected @@ -88,5 +108,19 @@ class Repository < ActiveRecord::Base raise "Failed to delete repository #{name} inside platform #{platform.name}." end end + + def symlink_downloads_path + "#{ DOWNLOADS_PATH }/#{ self.platform.unixname }" + end + + def add_downloads_symlink + raise "Personal platform path #{ symlink_downloads_path } already exists!" if File.exists?(symlink_downloads_path) && File.directory?(symlink_downloads_path) + FileUtils.symlink platform.path, symlink_downloads_path + end + + def remove_downloads_symlink + raise "Personal platform path #{ symlink_downloads_path } does not exists!" if !(File.exists?(symlink_downloads_path) && File.directory?(symlink_downloads_path)) + FileUtils.rm_rf symlink_downloads_path + end end diff --git a/app/models/user.rb b/app/models/user.rb index 6374b5bb7..5fe0b8d41 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -20,7 +20,13 @@ class User < ActiveRecord::Base has_many :platforms, :through => :targets, :source => :target, :source_type => 'Platform', :autosave => true has_many :repositories, :through => :targets, :source => :target, :source_type => 'Repository', :autosave => true + include PersonalRepository + validates :uname, :presence => true, :uniqueness => {:case_sensitive => false}, :format => { :with => /^[a-zA-Z0-9_]+$/ }, :allow_nil => false, :allow_blank => false + #TODO: Replace this simple cross-table uniq validation by more progressive analog + validate lambda { + errors.add(:uname, I18n.t('flash.user.group_uname_exists')) if Group.exists? :uname => uname + } attr_accessible :email, :password, :password_confirmation, :remember_me, :login, :name, :ssh_key, :uname attr_readonly :uname diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 5a28083e1..3b4b1e2b7 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -37,6 +37,9 @@ -if current_user.can_perform?('roles', 'index') %li{:class => controller.controller_path == 'roles' ? 'active' : '' } %a{:href => roles_path}= t("layout.menu.roles") + -if current_user.can_perform?('personal_repositories', 'index') + %li{:class => controller.controller_path == 'personal_repositories' ? 'active' : '' } + %a{:href => personal_repository_path( current_user.personal_repository.id )}= t("layout.menu.personal_repository") #wrapper.wat-cf = render :partial => "layouts/flashes" #main diff --git a/app/views/personal_repositories/_proj_list.html.haml b/app/views/personal_repositories/_proj_list.html.haml new file mode 100644 index 000000000..f4cce088a --- /dev/null +++ b/app/views/personal_repositories/_proj_list.html.haml @@ -0,0 +1,11 @@ +%table.table + %tr + %th.first= t("activerecord.attributes.project.name") + %th.last   + - @projects.each do |project| + %tr{:class => cycle("odd", "even")} + %td + = link_to project.owner.name + '/' + project.name, project_path(project) + %td.last + #{link_to t("layout.show"), project_path(project)} | #{link_to t("layout.add"), url_for(:controller => :personal_repositories, :action => :add_project, :project_id => project.id)} + diff --git a/app/views/personal_repositories/_proj_list1.html.haml b/app/views/personal_repositories/_proj_list1.html.haml new file mode 100644 index 000000000..c37d33bf7 --- /dev/null +++ b/app/views/personal_repositories/_proj_list1.html.haml @@ -0,0 +1,10 @@ +%table.table + %tr + %th.first= t("activerecord.attributes.project.name") + %th.last   + - @projects.each do |project| + %tr{:class => cycle("odd", "even")} + %td + = link_to project.name, project_path(project) + %td.last + #{link_to t("layout.show"), project_path(project)} | #{link_to t("layout.delete"), url_for (:action => :remove_project, :project_id => project.id), :confirm => t("layout.projects.confirm_delete")} diff --git a/app/views/personal_repositories/projects_list.html.haml b/app/views/personal_repositories/projects_list.html.haml new file mode 100644 index 000000000..ac9ea498f --- /dev/null +++ b/app/views/personal_repositories/projects_list.html.haml @@ -0,0 +1,59 @@ +.block + .secondary-navigation + %ul.wat-cf + %li.first.active= link_to t("layout.personal_repositories.show"), personal_repository_path(@repository) + %li= link_to t("layout.personal_repositories.settings"), settings_personal_repository_path(@repository) + .content + .inner + %p + %b + = t("activerecord.attributes.repository.name") + \: + = @repository.name + %p + %b + = t("activerecord.attributes.repository.unixname") + \: + = @repository.unixname + %p + %b + = t("activerecord.attributes.repository.platform") + \: + = link_to @repository.platform.name, url_for(@repository.platform) + %p + %b + = t("activerecord.attributes.repository.owner") + \: + = link_to @repository.owner.name, url_for(@repository.owner) + %p + %b + = t("activerecord.attributes.repository.visibility") + \: + = @repository.visibility + %p + %b + = t("activerecord.attributes.repository.platform") + \: + = link_to @repository.platform.name, platform_path(@platform) + .wat-cf + = link_to image_tag("web-app-theme/icons/cross.png", :alt => t("layout.delete")) + " " + t("layout.delete"), @repository_path, :method => "delete", :class => "button", :confirm => t("layout.repositories.confirm_delete") + +%a{ :name => "projects" } +.block + .secondary-navigation + %ul.wat-cf + %li.first= link_to t("layout.projects.list"), personal_repository_path(@repository) + "#projects" + %li.active= link_to t("layout.projects.add"), add_project_personal_repository_path(@repository) + .content + %h2.title + = t("layout.projects.list_header") + .inner + = render :partial => 'shared/search_form' + = render :partial => 'proj_list', :object => @projects + .actions-bar.wat-cf + .actions + = will_paginate @projects, :param_name => :project_page + + +-# content_for :sidebar, render(:partial => 'sidebar') + diff --git a/app/views/personal_repositories/settings.html.haml b/app/views/personal_repositories/settings.html.haml new file mode 100644 index 000000000..ab691275d --- /dev/null +++ b/app/views/personal_repositories/settings.html.haml @@ -0,0 +1,20 @@ +.block + .secondary-navigation + %ul.wat-cf + %li.first= link_to t("layout.personal_repositories.show"), personal_repository_path(@repository) + %li.active= link_to t("layout.personal_repositories.settings"), settings_personal_repository_path(@repository) + .content + %h2.title= t("layout.personal_repositories.settings_header") + .inner + .group + %span= t("activerecord.attributes.repository.visibility") + ":" + %span + %i= t("activerecord.attributes.repository.visibility_types.#{ @repository.visibility }") + .group + = link_to t("layout.personal_repositories.change_visibility_from_#{ @repository.visibility }"), change_visibility_personal_repository_path(@repository) + + %br + .group + %span= @urmpi_command + +-# content_for :sidebar, render(:partial => 'sidebar') diff --git a/app/views/personal_repositories/show.html.haml b/app/views/personal_repositories/show.html.haml new file mode 100644 index 000000000..323fbe9df --- /dev/null +++ b/app/views/personal_repositories/show.html.haml @@ -0,0 +1,33 @@ +.block + .secondary-navigation + %ul.wat-cf + %li.first.active= link_to t("layout.personal_repositories.show"), personal_repository_path(@repository) + %li= link_to t("layout.personal_repositories.settings"), settings_personal_repository_path(@repository) + .content + .inner + %p + %b + = t("activerecord.attributes.repository.owner") + \: + = link_to @repository.owner.name, url_for(@repository.owner) + .wat-cf + =# link_to image_tag("web-app-theme/icons/cross.png", :alt => t("layout.delete")) + " " + t("layout.delete"), @repository_path, :method => "delete", :class => "button", :confirm => t("layout.repositories.confirm_delete") + +%a{ :name => "projects" } +.block + .secondary-navigation + %ul.wat-cf + %li.first.active= link_to t("layout.projects.list"), personal_repository_path(@repository) + "#projects" + %li= link_to t("layout.projects.add"), add_project_personal_repository_path(@repository) + .content + %h2.title + = t("layout.projects.list_header") + .inner + = render :partial => 'shared/search_form' + = render :partial => 'proj_list1', :object => @projects + .actions-bar.wat-cf + .actions + = will_paginate @projects, :param_name => :project_page + + +-# content_for :sidebar, render(:partial => 'sidebar') diff --git a/app/views/private_users/index.html.haml b/app/views/private_users/index.html.haml index d43dd15d2..719f7174f 100644 --- a/app/views/private_users/index.html.haml +++ b/app/views/private_users/index.html.haml @@ -23,7 +23,7 @@ .secondary-navigation %ul.wat-cf %li.first.active= link_to t("layout.private_users.list"), platform_private_users_path(@platform) - %li= link_to t("layout.private_users.new"), platform_private_users_path(@platform), :method => :post + %li= link_to t("layout.private_users.new"), platform_private_users_path(@platform), :method => :post if PrivateUser.can_generate_more?(current_user.id) .content %h2.title = t("layout.private_users.list_header") diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 1e4011581..791441008 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -48,6 +48,7 @@ ru: rights: Права roles: Роли users: Пользователи + personal_repository: Мой репозиторий sessions: sign_in_header: Вход в систему @@ -122,6 +123,13 @@ ru: back_to_the_list: ⇐ К списку репозиториев confirm_delete: Вы уверены, что хотите удалить этот репозиторий? current_repository_header: Текущий репозиторий + + personal_repositories: + settings_header: Настройки + change_visibility_from_hidden: Сменить статус на "Открытый" + change_visibility_from_open: Сменить статус на "Приватный" + settings: Настройки + show: Мой репозиторий products: list: Список @@ -255,11 +263,13 @@ ru: saved: Пользователь успешно сохранен save_error: Не удалось сохранить данные о пользователе destroyed: Учетная запись успешно удалена + group_uname_exists: Группа с таким именем уже зарегестрирована group: saved: Группа успешно сохранена save_error: Не удалось сохранить группу destroyed: Группа успешно удалена + user_uname_exists: Пользователь с таким именем уже зарегестрирован repository: saved: Репозиторий успешно добавлен @@ -335,6 +345,11 @@ ru: unixname: Unixname created_at: Создан updated_at: Обновлен + owner: Владелец + visibility: Статус + visibility_types: + open: Открытый + hidden: Закрытый product: name: Название diff --git a/config/routes.rb b/config/routes.rb index 604ee67f4..2ce573f21 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,7 +2,6 @@ Rosa::Application.routes.draw do # XML RPC match 'api/xmlrpc' => 'rpc#xe_index' - devise_for :users, :controllers => {:omniauth_callbacks => 'users/omniauth_callbacks'} do get '/users/auth/:provider' => 'users/omniauth_callbacks#passthru' end @@ -31,6 +30,15 @@ Rosa::Application.routes.draw do match 'build_lists/' => 'build_lists#all', :as => :all_build_lists match 'build_lists/:id/cancel/' => 'build_lists#cancel', :as => :build_list_cancel + resources :personal_repositories, :only => [:show] do + member do + get :settings + get :change_visibility + get :add_project + get :remove_project + end + end + resources :platforms do resources :private_users diff --git a/db/migrate/20111026152530_add_user_id_to_private_users.rb b/db/migrate/20111026152530_add_user_id_to_private_users.rb new file mode 100644 index 000000000..2a31380c8 --- /dev/null +++ b/db/migrate/20111026152530_add_user_id_to_private_users.rb @@ -0,0 +1,9 @@ +class AddUserIdToPrivateUsers < ActiveRecord::Migration + def self.up + add_column :private_users, :user_id, :integer + end + + def self.down + remove_column :private_users, :user_id + end +end \ No newline at end of file diff --git a/db/schema.rb b/db/schema.rb index bd0d9380a..433a30804 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20111026135125) do +ActiveRecord::Schema.define(:version => 20111026152530) do create_table "arches", :force => true do |t| t.string "name", :null => false @@ -158,6 +158,7 @@ ActiveRecord::Schema.define(:version => 20111026135125) do t.string "password" t.datetime "created_at" t.datetime "updated_at" + t.integer "user_id" end create_table "products", :force => true do |t| @@ -207,7 +208,6 @@ ActiveRecord::Schema.define(:version => 20111026135125) do t.string "object_type" t.integer "target_id" t.string "target_type" - t.integer "role_id" t.datetime "created_at" t.datetime "updated_at" end @@ -268,9 +268,8 @@ ActiveRecord::Schema.define(:version => 20111026135125) do t.datetime "remember_created_at" t.datetime "created_at" t.datetime "updated_at" - t.string "uname" t.text "ssh_key" - t.integer "role_id" + t.string "uname" t.integer "global_role_id" end diff --git a/public/downloads/readme.txt b/public/downloads/readme.txt new file mode 100644 index 000000000..355139953 --- /dev/null +++ b/public/downloads/readme.txt @@ -0,0 +1 @@ +Hello! This is a directory to share open repositories within. diff --git a/spec/controllers/personal_repositories_controller_spec.rb b/spec/controllers/personal_repositories_controller_spec.rb new file mode 100644 index 000000000..1ca1fa29c --- /dev/null +++ b/spec/controllers/personal_repositories_controller_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe PersonalRepositoriesController do + +end diff --git a/spec/helpers/personal_repositories_helper_spec.rb b/spec/helpers/personal_repositories_helper_spec.rb new file mode 100644 index 000000000..1aed8ddb2 --- /dev/null +++ b/spec/helpers/personal_repositories_helper_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +# Specs in this file have access to a helper object that includes +# the PersonalRepositoriesHelper. For example: +# +# describe PersonalRepositoriesHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# helper.concat_strings("this","that").should == "this that" +# end +# end +# end +describe PersonalRepositoriesHelper do + pending "add some examples to (or delete) #{__FILE__}" +end