Merge branch '8784-dir-listings'
[arvados.git] / services / api / db / migrate / 20150317132720_add_username_to_users.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 require 'has_uuid'
6 require 'kind_and_etag'
7
8 class AddUsernameToUsers < ActiveRecord::Migration
9   include CurrentApiClient
10
11   SEARCH_INDEX_COLUMNS =
12     ["uuid", "owner_uuid", "modified_by_client_uuid",
13      "modified_by_user_uuid", "email", "first_name", "last_name",
14      "identity_url", "default_owner_uuid"]
15
16   class ArvadosModel < ActiveRecord::Base
17     self.abstract_class = true
18     extend HasUuid::ClassMethods
19     include CurrentApiClient
20     include KindAndEtag
21     before_create do |record|
22       record.uuid ||= record.class.generate_uuid
23       record.owner_uuid ||= system_user_uuid
24     end
25     serialize :properties, Hash
26
27     def self.to_s
28       # Clean up the name of the stub model class so we generate correct UUIDs.
29       super.rpartition("::").last
30     end
31   end
32
33   class Log < ArvadosModel
34     def self.log_for(thing, age="old")
35       { "#{age}_etag" => thing.etag,
36         "#{age}_attributes" => thing.attributes,
37       }
38     end
39
40     def self.log_create(thing)
41       new_log("create", thing, log_for(thing, "new"))
42     end
43
44     def self.log_update(thing, start_state)
45       new_log("update", thing, start_state.merge(log_for(thing, "new")))
46     end
47
48     def self.log_destroy(thing)
49       new_log("destroy", thing, log_for(thing, "old"))
50     end
51
52     private
53
54     def self.new_log(event_type, thing, properties)
55       create!(event_type: event_type,
56               event_at: Time.now,
57               object_uuid: thing.uuid,
58               object_owner_uuid: thing.owner_uuid,
59               properties: properties)
60     end
61   end
62
63   class Link < ArvadosModel
64   end
65
66   class User < ArvadosModel
67   end
68
69   def sanitize_username(username)
70     username.
71       sub(/^[^A-Za-z]+/, "").
72       gsub(/[^A-Za-z0-9]/, "")
73   end
74
75   def usernames_wishlist(user)
76     usernames = Hash.new(0)
77     usernames[user.email.split("@", 2).first] += 1
78     Link.
79        where(tail_uuid: user.uuid, link_class: "permission", name: "can_login").
80        find_each do |login_perm|
81       username = login_perm.properties["username"]
82       usernames[username] += 2 if (username and not username.empty?)
83     end
84     usernames.keys.
85       sort_by { |n| -usernames[n] }.
86       map { |n| sanitize_username(n) }.
87       reject(&:empty?)
88   end
89
90   def increment_username(username)
91     @username_suffixes[username] += 1
92     "%s%i" % [username, @username_suffixes[username]]
93   end
94
95   def each_wanted_username(user)
96     usernames = usernames_wishlist(user)
97     usernames.each { |n| yield n }
98     base_username = usernames.first || "arvadosuser"
99     loop { yield increment_username(base_username) }
100   end
101
102   def recreate_search_index(columns)
103     remove_index :users, name: "users_search_index"
104     add_index :users, columns, name: "users_search_index"
105   end
106
107   def up
108     @username_suffixes = Hash.new(1)
109     add_column :users, :username, :string, null: true
110     add_index :users, :username, unique: true
111     recreate_search_index(SEARCH_INDEX_COLUMNS + ["username"])
112
113     [Link, Log, User].each { |m| m.reset_column_information }
114     User.validates(:username, uniqueness: true, allow_nil: true)
115     User.where(is_active: true).order(created_at: :asc).find_each do |user|
116       start_log = Log.log_for(user)
117       each_wanted_username(user) do |username|
118         user.username = username
119         break if user.valid?
120       end
121       user.save!
122       Log.log_update(user, start_log)
123     end
124   end
125
126   def down
127     remove_index :users, :username
128     recreate_search_index(SEARCH_INDEX_COLUMNS)
129     remove_column :users, :username
130   end
131 end