OSDN Git Service

Model::Users: fix to inherit transaction state from Users instance
[newslash/newslash.git] / src / newslash_web / lib / Newslash / Model / Users.pm
1 package Newslash::Model::Users;
2 use Newslash::Model::Base -base;
3
4 use Newslash::Model::Users::Friends;
5 use Newslash::Model::Users::Util;
6 use Newslash::Model::Users::ACL2;
7 use Newslash::Model::Users::Prefs;
8 use Newslash::Model::Users::Configs;
9 use Newslash::Model::Users::Achievements;
10 use Newslash::Model::Users::Passwords;
11 use Newslash::Model::Users::Info;
12 use Newslash::Model::Users::ACL;
13 use Newslash::Model::Users::Comments;
14 use Newslash::Model::Users::User;
15 use Newslash::Model::Users::Sidebar;
16 use Newslash::Model::Users::Messages;
17 use Newslash::Model::Users::Index;
18 use Newslash::Model::Users::Class;
19 use Newslash::Model::Users::Param;
20 use Newslash::Model::Users::Permissions;
21
22 use Newslash::Util::Timezones;
23
24 use Email::Valid;
25 use Data::Dumper;
26 use Mojo::JSON qw(to_json from_json);
27
28 use Encode qw(encode_utf8 decode_utf8);
29 use Mojo::Log;
30
31 # nickname, matchname, and realemail columns doesn't have UNIQUE limitation,
32 # but practically they are unique
33 sub key_definition {
34     return {
35             table => "users",
36             primary => "uid",
37             unique => [qw(nickname realemail matchname)],
38             datetime => [qw(newpasswd_ts journal_last_entry_date)],
39             other => [qw(fakeemail homepage
40                          passwd sig seclev newpasswd
41                          author shill_id)],
42             aliases => { user_id => "uid",
43                          id => "uid",
44                        }
45            };
46 }
47
48 sub on_start_up {
49     my $self = shift;
50
51     # create tables
52     return 1 if $self->check_readonly;
53
54     # sidebar
55     $self->sidebar->on_start_up;
56
57     # create `ns_users_config` table
58     $self->configs->_check_and_create_table;
59
60     # check anonymous user exists
61     my $anon = $self->anonymous_user;
62     if (!$anon) {
63         $self->hard_delete(1);
64         $self->create("Anonymous Coward", 'AnonymousCoward@example.com', {uid => 1});
65         $self->update(uid => 1, seclev => 0);
66         $self->info->update(uid => 1,
67                             realname => "名無しのゴンベエ",
68                             karma => -25);
69         $self->warn("create anonymous_user");
70     }
71
72     # convert AC (uid==1) user's data
73     $self->prefs->convert_old_prefs(1);
74     return 1;
75 }
76
77 ##### sub models
78 sub friends      { return shift->_get_model("_friends",      '::Users::Friends'); }
79 sub prefs        { return shift->_get_model("_prefs",        '::Users::Prefs'); }
80 sub configs      { return shift->_get_model("_configs",      '::Users::Configs'); }
81 sub achievements { return shift->_get_model("_achievements", '::Users::Achievements'); }
82 sub passwords    { return shift->_get_model("_passwords",    '::Users::Passwords'); }
83 sub info         { return shift->_get_model("_info",         '::Users::Info'); }
84 sub acl          { return shift->_get_model("_acl",          '::Users::ACL'); }
85 sub acl2         { return shift->_get_model("_acl2",         '::Users::ACL2'); }
86 sub comments     { return shift->_get_model("_comments",     '::Users::Comments'); }
87 sub sidebar      { return shift->_get_model("_sidebar",      '::Users::Sidebar'); }
88 sub messages     { return shift->_get_model("_messages",     '::Users::Messages'); }
89 sub index        { return shift->_get_model("_index",        '::Users::Index'); }
90 sub class        { return shift->_get_model("_class",        '::Users::Class'); }
91 sub param        { return shift->_get_model("_param",        '::Users::Param'); }
92 sub permissions  { return shift->_get_model("_permissions",  '::Users::Permissions'); }
93
94 sub _get_model {
95     my ($self, $name, $class) = @_;
96     if (!$self->{$name}) {
97         $self->{$name} = $self->new_instance_of($class);
98     }
99     else {
100         if ($self->transaction_mode && !$$self->{$name}->transaction_mode) {
101             $self->{$name}->use_transaction($self->{_tr_dbh});
102         }
103     }
104     return $self->{$name};
105 }
106
107 ##### utility functions
108
109 # auth related
110 sub authentification {
111     return shift->passwords->authentification(@_);
112 }
113
114 sub is_anonymous_uid {
115     my ($self, $uid) = @_;
116     return ($uid == 1);
117 }
118
119 sub anonymous_user {
120     my $self = shift;
121     return  $self->select(uid => 1);
122 }
123
124 #========================================================================
125
126 =head2 nickname_to_matchname
127
128 Convert nickname to matchname.
129
130 nickname consists of almost all alphabet, number, symbol.
131
132 matchname consists of lower alphabet and number.
133
134 =cut
135
136 sub nickname_to_matchname {
137     my $self = shift;
138     my $nick = shift;
139     $nick = lc $nick;
140     $nick =~ s/[^a-zA-Z0-9]//g;
141     return $nick;
142 }
143
144 sub update_karma {
145     my $self = shift;
146     my $params = {@_};
147
148     my $uid = $params->{uid};
149     return if !$uid;
150
151     my $add = $params->{add};
152     my $karma => $params->{karma};
153     return if (!defined $karma && !defined $add);
154
155     my $rs = 0;
156     if (defined $karma) {
157         $rs = $self->info->update(uid => $uid, karma => $karma);
158         return if !defined $rs;
159     }
160
161     if (defined $add) {
162         my $rs1 = $self->info->update(uid => $uid,
163                                       karma => {add => $add});
164         return if !defined $rs1;
165         $rs += $rs1;
166     }
167
168     return $rs;
169 }
170
171 ##### select/create/update/delete
172 sub select {
173     my $self = shift;
174     my $params = {@_};
175     my $rs = $self->generic_select(params => $params);
176     return if (!defined $rs);
177     return $self->_create_user_object($rs);
178 }
179
180 sub get_user_config {
181     my ($self, $users) = @_;
182     my $target = (ref($users) eq "HASH") ? [$users,] : $users;
183
184     for my $u (@$target) {
185         $u->{config} = $self->configs->select(id => $u->{uid});
186         $u->{configJSON} = to_json($u->{config});
187     }
188 }
189
190 sub _create_user_object {
191     my ($self, $user) = @_;
192
193     my $_fn = sub {
194         my $user = shift;
195         my $result = {%$user};
196         #for my $k (qw{uid nickname realemail fakeemail homepage sig seclev matchname author}) {
197         #    $result->{$k} = $user->{$k};
198         #}
199         # if seclev is greater than 10000, the user is admin
200         # if seclev is 0, the user is Anonymous user
201         $result->{is_admin} = $result->{seclev} >= 10000 ? 1 : 0;
202         $result->{is_login} = $result->{seclev} != 0 ? 1 : 0;
203
204         #$result->{config} = $self->configs->select(id => $user->{uid});
205         #$result->{configJSON} = to_json($result->{config});
206
207         return $result;
208     };
209
210     if (ref($user) eq 'ARRAY') {
211         my @ret = map { $_fn->($_) } @$user;
212         return \@ret;
213     }
214     else {
215         return $_fn->($user);
216     }
217 }
218
219 sub update {
220     my $self = shift;
221     my $params = {@_};
222
223     # check password
224     if ($params->{passwd}) {
225         $params->{passwd} = $self->passwords->encrypt_password($params->{passwd});
226     }
227
228     return $self->generic_update(params => $params);
229 }
230
231 sub hard_delete {
232     my ($self, $uid) = @_;
233     return if $self->check_readonly;
234     my $error = 0;
235
236     my $dbh = $self->connect_db({AutoCommit => 0,});
237     for my $table (qw(users users_info users_prefs
238                       users_comments users_hits users_class
239                       users_index users_param)) {
240         my $sql = "DELETE FROM $table WHERE uid = ?";
241         my $rs = $dbh->do($sql, undef, $uid);
242         if (!defined $rs || $rs == 0) {
243             Mojo::Log->new->warn("DELETE FROM $table failed. uid is $uid.");
244             $error = 1;
245         }
246     }
247     $dbh->commit;
248     $self->disconnect_db;
249
250     return !$error;
251 }
252
253 #========================================================================
254
255 =head2 create
256
257 Create new user.
258
259 =over 4
260
261 =item Return value
262
263 HASH
264
265 =back
266
267 =cut
268
269 sub create {
270     my ($self, $nickname, $email, $passwd, $opts) = @_;
271     return if $self->check_readonly;
272     $opts ||= {};
273
274     # check: nickname is given?
275     if ($nickname =~ m/\A\s+\z/) {
276         $self->set_error("INVALID_ID");
277         return;
278     }
279
280     my $matchname = $self->nickname_to_matchname($nickname);
281
282     # check: email is valid?
283     if (!Email::Valid->address($email)) {
284         $self->set_error("INVALID_EMAIL");
285         return;
286     }
287
288     # check: nickname already exists?
289     my $user = $self->select(nickname => $nickname);
290     if ($user) {
291         $self->set_error("ID_EXISTS");
292         return;
293     }
294
295     # check: mail already registered?
296     $user = $self->select(realemail => $email);
297     if ($user) {
298         $self->set_error("EMAIL_EXISTS");
299         return;
300     }
301
302     my $dbh = $self->start_transaction;
303     my $sql;
304     my @params;
305     my $sth;
306
307     # create user and set temporary password
308     if (!$passwd) {
309         $passwd = $self->passwords->generate_random_password;
310     }
311     my $enc_passwd = $self->passwords->encrypt_password($passwd);
312     my $newpasswd = $opts->{password_token};
313
314     # build SQL and parameters
315     if ($opts->{password_token}) {
316         $sql = <<"EOSQL";
317 INSERT INTO users (uid, realemail, nickname, matchname,  seclev,  passwd, newpasswd, newpasswd_ts)
318   VALUES          (?,   ?,         ?,        ?,          ?,       ?,      ?,         NOW())
319 EOSQL
320         @params = ($opts->{uid},
321                    $email,
322                    $nickname,
323                    $matchname,
324                    $opts->{seclev} // 1,
325                    $enc_passwd,
326                    $opts->{password_token},
327                   );
328     }
329     else {
330         $sql = <<"EOSQL";
331 INSERT INTO users (uid, realemail, nickname, matchname,  seclev,  passwd)
332   VALUES          (?,   ?,         ?,        ?,          ?,       ?)
333 EOSQL
334         @params = ($opts->{uid}, $email, $nickname, $matchname, 1, $enc_passwd);
335     }
336
337     $dbh->do($sql, undef, @params);
338     my $uid = $dbh->last_insert_id(undef, undef, undef, undef);
339
340     if (!$uid) {
341         $self->rollback;
342         $self->set_error($dbh->errstr, $dbh->err);
343         return;
344     }
345
346     # create users_info, etc.
347     my $result;
348     $sql = <<"EOSQL";
349 INSERT INTO users_info (uid, lastaccess, created_at, bio)
350   VALUES               (?,   NOW(),      NOW(),      ?)
351 EOSQL
352     $result = $dbh->do($sql, undef, $uid, '');
353     if (!$result) {
354         $self->rollback;
355         $self->set_error($dbh->errstr, $dbh->err);
356         return;
357     }
358
359     for my $table (qw{users_prefs users_comments users_hits users_class}) {
360         $sql = "INSERT INTO $table (uid) VALUES (?)";
361         $result = $dbh->do($sql, undef, $uid);
362         if (!$result) {
363             $self->rollback;
364             $self->set_error($dbh->errstr, $dbh->err);
365             return;
366         }
367     }
368
369     $sql = <<"EOSQL";
370 INSERT INTO users_index (uid, story_never_topic, slashboxes, story_always_topic)
371   VALUES                (?,   ?,                 ?,          ?)
372 EOSQL
373     $result = $dbh->do($sql, undef, $uid, '', '', '');
374     if (!$result) {
375         $self->rollback;
376         $self->set_error($dbh->errstr, $dbh->err);
377         return;
378     }
379
380     # finish
381     $self->commit;
382     return $uid;
383 }
384
385
386 1;