Changeset 899
- Timestamp:
- 02/29/08 09:56:24
- Files:
-
- apps/notifier/app/apis (deleted)
- apps/notifier/app/controllers/application_controller.php (deleted)
- apps/notifier/app/controllers/test_controller.php (deleted)
- apps/notifier/app/helpers/application_helper.php (deleted)
- apps/notifier/app/i18n (deleted)
- apps/notifier/app/models/favorite.php (added)
- apps/notifier/app/models/status.php (modified) (2 diffs)
- apps/notifier/app/models/user.php (modified) (2 diffs)
- apps/notifier/app/resources/base_resource.php (modified) (2 diffs)
- apps/notifier/app/resources/favorite_resource.php (added)
- apps/notifier/app/resources/favorites_resource.php (added)
- apps/notifier/app/resources/friend_resource.php (added)
- apps/notifier/app/resources/friends_resource.php (modified) (2 diffs)
- apps/notifier/app/resources/friends_timeline_resource.php (modified) (1 diff)
- apps/notifier/app/resources/public_timeline_resource.php (added)
- apps/notifier/app/resources/status_resource.php (added)
- apps/notifier/app/resources/statuses_resource.php (modified) (1 diff)
- apps/notifier/app/resources/user_resource.php (added)
- apps/notifier/app/resources/users_resource.php (modified) (2 diffs)
- apps/notifier/app/views/layouts (deleted)
- apps/notifier/app/views/test (deleted)
- apps/notifier/conf/routes.php (modified) (1 diff)
- apps/notifier/db/schema_db.sql (added)
- apps/notifier/db/test_data.sql (modified) (1 diff)
- apps/notifier/public/.htaccess (modified) (1 diff)
- apps/notifier/public/images/fav.png (added)
- apps/notifier/public/images/not_fav.png (added)
- apps/notifier/public/index.html (added)
- apps/notifier/public/js/base64.js (deleted)
- apps/notifier/public/js/notifier.js (modified) (9 diffs)
- apps/notifier/public/js/wsse.js (added)
- apps/notifier/public/styles/main.css (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
apps/notifier/app/models/status.php
r894 r899 6 6 { 7 7 return $this->by_sql( 8 "SELECT statuses.* FROM statuses8 "SELECT statuses.*, favorites.id AS favorite_id FROM statuses 9 9 LEFT OUTER JOIN users ON users.id = statuses.sender_id 10 10 LEFT OUTER JOIN friendships ON statuses.sender_id = friendships.friend_id 11 LEFT OUTER JOIN favorites ON (favorites.user_id = friendships.user_id AND favorites.status_id = statuses.id) 11 12 WHERE friendships.user_id = '{$user_id}' OR statuses.sender_id = '{$user_id}' 12 13 ORDER BY timestamp DESC LIMIT {$limit}" … … 22 23 ); 23 24 25 public function validate() 26 { 27 $this->validate_presence_of(array('text', 'sender_id')); 28 } 29 24 30 public function serializable_form() 25 31 { 26 return parent::serializable_form(array('include' => array('sender'))); 32 $obj = parent::serializable_form(array('include' => array('sender'))); 33 if (array_key_exists('favorite_id', $this->values)) { 34 $obj->favorite_id = $this->values['favorite_id']; 35 } 36 return $obj; 27 37 } 28 38 } apps/notifier/app/models/user.php
r894 r899 5 5 public static $objects; 6 6 public static $relationships = array( 7 'statuses' => array('assoc_type' => 'has_many', 'foreign_key' => 'sender_id'), 8 9 'favorites' => array('assoc_type' => 'many_to_many', 'join_table' => 'favorites', 10 'class_name' => 'Status'), 7 11 'friends' => array('assoc_type' => 'many_to_many', 'join_table' => 'friendships', 8 'class_name' => 'User' )12 'class_name' => 'User', 'association_foreign_key' => 'friend_id') 9 13 ); 10 14 11 public static function authenticate($login , $password)15 public static function authenticate($login) 12 16 { 13 17 try { 14 return User::$objects->get('name = ?', 'pwd = ?', array($login, md5($password))); 18 $user = User::$objects->get('name = ?', array($login)); 19 return array($user, $user->pwd); 15 20 } catch (SRecordNotFound $e) { 16 21 return false; … … 21 26 { 22 27 $this->validate_presence_of(array('name', 'email', 'pwd'), 23 array('message' => 'est obligatoire')); 24 $this->validate_uniqueness_of('name', array('message' => 'est déjà pris')); 28 array('message' => 'est obligatoire')); 29 $this->validate_format_of('name', array('pattern' => 'alphanum', 30 'message' => 'doit être alphanumérique')); 31 $this->validate_format_of('email', array('pattern' => 'email', 32 'message' => 'ne semble pas être valide')); 25 33 } 26 34 apps/notifier/app/resources/base_resource.php
r894 r899 1 1 <?php 2 2 3 require STATO_CORE_PATH.'/components/http_authentication/lib/ basic_http_authentication.php';3 require STATO_CORE_PATH.'/components/http_authentication/lib/wsse_http_authentication.php'; 4 4 5 5 class BaseResource extends SResource … … 8 8 protected $user; 9 9 10 public function __construct() 11 { 12 parent::__construct(); 13 $this->before_filters->append('authenticate'); 14 $this->before_filters->append('must_specify_user'); 15 $this->before_filters->append('specified_user_must_be_authenticated_user'); 16 } 17 10 18 protected function authenticate() 11 19 { 12 20 $this->authenticated_user 13 = SBasicHttpAuthentication::authenticate_or_request( 14 $this->request, $this->response, array('User', 'authenticate'), 'Notifier'); 21 = SWsseHttpAuthentication::authenticate_or_request( 22 $this->request, $this->response, array('User', 'authenticate'), 'Notifier' 23 ); 24 if (!$this->authenticated_user) return false; 15 25 } 16 26 apps/notifier/app/resources/friends_resource.php
r894 r899 3 3 class FriendsResource extends BaseResource 4 4 { 5 public function __construct()5 public function initialize() 6 6 { 7 $this-> add_before_filter('authenticate');8 $this->add_before_filter('must_specify_user');7 $this->before_filters->skip('specified_user_must_be_authenticated_user', 8 array('only' => 'get')); 9 9 } 10 10 … … 13 13 return $this->user->friends->all(); 14 14 } 15 16 public function post() 17 { 18 try { 19 $friend = User::$objects->get_by_name($this->params['name']); 20 $this->user->friends->add($friend); 21 if ($this->user->save()) { 22 $this->responds_created($friend, 201); 23 } else { 24 $this->responds_error(400); 25 } 26 } catch (SRecordNotFound $e) { 27 $this->responds_detailed_error('Unknown user !', 400); 28 } 29 } 15 30 } 16 31 apps/notifier/app/resources/friends_timeline_resource.php
r894 r899 3 3 class FriendsTimelineResource extends BaseResource 4 4 { 5 public function __construct()5 public function initialize() 6 6 { 7 $this->add_before_filter('authenticate'); 8 $this->add_before_filter('must_specify_user'); 7 $this->before_filters->skip('specified_user_must_be_authenticated_user'); 9 8 } 10 9 11 10 public function get() 12 11 { 13 return Status::$objects->from_friends_of($this->user->id, 20); 12 $statuses = Status::$objects->from_friends_of($this->user->id, 20); 13 $this->responds($statuses, 200); 14 14 } 15 15 } apps/notifier/app/resources/statuses_resource.php
r894 r899 3 3 class StatusesResource extends BaseResource 4 4 { 5 public function __construct()5 public function initialize() 6 6 { 7 $this->add_before_filter('authenticate'); 8 $this->add_before_filter('must_specify_user'); 9 $this->add_before_filter('specified_user_must_be_authenticated_user', 10 array('except' => 'get')); 7 $this->before_filters->skip('specified_user_must_be_authenticated_user', 8 array('only' => 'get')); 11 9 } 12 10 13 11 public function get() 14 12 { 15 return Status::$objects->get($this->params['id']); 13 $statuses = $this->user->statuses->limit(20)->order_by('-timestamp'); 14 $this->responds($statuses, 200); 16 15 } 17 16 18 17 public function post() 19 18 { 20 $ note= new Status();21 $ note->text = $this->params['text'];22 $ note->sender_id = $this->user->id;23 $note->save();24 $note->reload();25 return $note;26 }27 28 public function delete()29 {30 Status::$objects->get($this->params['id'])->delete();19 $status = new Status(); 20 $status->text = $this->params['text']; 21 $status->sender_id = $this->user->id; 22 23 if ($status->save()) { 24 // nous rechargeons le statut afin de disposer du champ 'timestamp' 25 $status->reload(); 26 $this->responds_created($status, 201); 27 } else { 28 $this->responds_detailed_error($status->errors, 400); 29 } 31 30 } 32 31 } apps/notifier/app/resources/users_resource.php
r894 r899 1 1 <?php 2 2 3 class UsersResource extends BaseResource3 class UsersResource extends SResource 4 4 { 5 public function __construct()6 {7 $this->add_before_filter('authenticate', array('except' => 'post'));8 }9 10 5 public function post() 11 6 { … … 15 10 $this->responds_error(409); 16 11 } catch (SRecordNotFound $e) { 17 $this->params['user']['pwd'] = md5($this->params['user']['pwd']);18 12 $user = new User($this->params['user']); 19 13 if ($user->save()) apps/notifier/conf/routes.php
r894 r899 2 2 3 3 $map = new SRouteSet(); 4 $map->connect('api/users/:username/statuses/:id', array('resource' => 'statuses')); 5 $map->connect('api/users/:username/timeline', array('resource' => 'user_timeline')); 6 $map->connect('api/users/:username/friends/timeline', array('resource' => 'friends_timeline')); 4 $map->connect('api/timeline', array('resource' => 'public_timeline')); 5 $map->connect('api/users', array('resource' => 'users')); 6 $map->connect('api/users/:username', array('resource' => 'user')); 7 $map->connect('api/users/:username/statuses', array('resource' => 'statuses')); 8 $map->connect('api/users/:username/statuses/:id', array('resource' => 'status')); 9 $map->connect('api/users/:username/timeline', array('resource' => 'friends_timeline')); 7 10 $map->connect('api/users/:username/friends', array('resource' => 'friends')); 8 $map->connect('api/users/:id', array('resource' => 'users')); 9 10 $map->connect(':controller/:action/:id'); 11 11 $map->connect('api/users/:username/friends/:id', array('resource' => 'friend')); 12 $map->connect('api/users/:username/favorites', array('resource' => 'favorites')); 13 $map->connect('api/users/:username/favorites/:id', array('resource' => 'favorite')); 12 14 return $map; 13 15 apps/notifier/db/test_data.sql
r894 r899 37 37 38 38 INSERT INTO `users` (`id`, `name`, `email`, `pwd`) VALUES 39 (1, 'tester1', 'tester1@test.com', ' 098f6bcd4621d373cade4e832627b4f6'),40 (2, 'tester2', 'tester2@test.com', ' 098f6bcd4621d373cade4e832627b4f6'),41 (3, 'tester3', 'tester3@test.com', ' 098f6bcd4621d373cade4e832627b4f6');39 (1, 'tester1', 'tester1@test.com', 'test'), 40 (2, 'tester2', 'tester2@test.com', 'test'), 41 (3, 'tester3', 'tester3@test.com', 'test'); apps/notifier/public/.htaccess
r835 r899 8 8 RewriteBase /notifier 9 9 10 #for google accelerator 11 #RewriteCond %{HTTP:x-moz} ^prefetch 12 #RewriteRule ^/*admin/.* - [F,L] 13 14 RewriteRule ^$ cache/index.html [QSA] 15 RewriteRule ^([^.]+)$ cache/$1.html [QSA] 10 RewriteRule ^$ index.html [QSA] 16 11 RewriteCond %{REQUEST_FILENAME} !-f 17 12 RewriteRule ^(.*)$ index.php [QSA,L] apps/notifier/public/js/notifier.js
r894 r899 8 8 notifier.actions = {}; 9 9 notifier.actions.login = function() { 10 notifier.prefs.username = $('#name').val(); 10 $('#login input:submit').attr('disabled', 'true'); 11 notifier.prefs.username = $('#name').val(); 11 12 notifier.prefs.password = $('#pwd').val(); 12 $('#login').hide(); 13 $('#main').show(); 14 notifier.actions.refreshFriendsTimeline(); 13 notifier.api.doRequest('users/'+notifier.prefs.username+'.json', { 14 success: function() { 15 $('#login input:submit').removeAttr('disabled'); 16 $('#login').hide(); 17 $('#main').show(); 18 notifier.actions.refreshFriendsTimeline(); 19 }, 20 error: function(error, status) { 21 $('#login input:submit').removeAttr('disabled'); 22 if (status == 401) { 23 alert("Accès refusé !"); 24 } else { 25 notifier.api.alertError(error, status); 26 } 27 $('#pwd').val(''); 28 } 29 }); 15 30 } 16 31 notifier.actions.register = function() { … … 25 40 params: data, 26 41 success: function() { 42 $('#register input:not(:submit)').val(''); 27 43 $('#register').hide(); 28 44 $('#login').show(); … … 38 54 } 39 55 notifier.actions.refreshFriendsTimeline = function() { 40 notifier.api.doRequest('users/'+notifier.prefs.username+'/ friends/timeline.json', {56 notifier.api.doRequest('users/'+notifier.prefs.username+'/timeline.json', { 41 57 success: function(notes) { 42 58 $('#friends-timeline').empty(); 43 for (var i in notes) { 44 $('#friends-timeline').append(notifier.ui.htmlForNote(notes[i])); 59 if (notes.length == 0) { 60 $('#friends-timeline').append('<p class="empty">Rien pour l\'instant</p>'); 61 } else { 62 for (var i in notes) { 63 $('#friends-timeline').append(notifier.ui.htmlForNote(notes[i])); 64 } 45 65 } 46 66 } … … 56 76 notifier.ui.prependNote(note); 57 77 $('#note_text').val(''); 58 $('#note_submit'). attr('disabled', 'false');78 $('#note_submit').removeAttr('disabled'); 59 79 }, 60 80 error: function(error, status) { 61 $('#note_submit'). attr('disabled', 'false');81 $('#note_submit').removeAttr('disabled'); 62 82 notifier.api.alertError(error, status); 83 } 84 }); 85 } 86 notifier.actions.editNote = function(noteId) { 87 notifier.api.doRequest('users/'+notifier.prefs.username+'/statuses/'+noteId+'.json', { 88 method: 'PUT', 89 params: {'text': $('#note_'+noteId+'_new_text').val()}, 90 success: function(note) { 91 $('#note_'+noteId+' p:first').text(note.text); 92 notifier.ui.hideEditForm(noteId); 63 93 } 64 94 }); … … 78 108 success: function(friends) { 79 109 $('#friends-list').empty(); 80 for (var i in friends) { 81 $('#friends-list').append(notifier.ui.htmlForFriend(friends[i])); 110 if (friends.length == 0) { 111 $('#friends-list').append('<p class="empty">Aucun ami pour l\'instant</p>'); 112 } else { 113 for (var i in friends) { 114 $('#friends-list').append(notifier.ui.htmlForFriend(friends[i])); 115 } 116 } 117 } 118 }); 119 } 120 notifier.actions.addFriend = function() { 121 $('#friend_submit').attr('disabled', 'true'); 122 notifier.api.doRequest('users/'+notifier.prefs.username+'/friends.json', { 123 method: 'POST', 124 params: {'name': $('#friend_name').val()}, 125 success: function(friend) { 126 $('#friends-list').prepend(notifier.ui.htmlForFriend(friend)); 127 $('#friend_name').val(''); 128 $('#friend_submit').removeAttr('disabled'); 129 }, 130 error: function(error, status) { 131 $('#friend_submit').removeAttr('disabled'); 132 notifier.api.alertError(error, status); 133 } 134 }); 135 } 136 notifier.actions.removeFriend = function(friendId) { 137 notifier.api.doRequest('users/'+notifier.prefs.username+'/friends/'+friendId+'.json', { 138 method: 'DELETE', 139 success: function() { 140 $('#friend_'+friendId).remove(); 141 } 142 }); 143 } 144 notifier.actions.addFavorite = function(noteId) { 145 notifier.api.doRequest('users/'+notifier.prefs.username+'/favorites.json', { 146 method: 'POST', 147 params: {'status_id': noteId}, 148 success: function(fav) { 149 var link = $('#note_'+noteId+' a:first'); 150 link.removeClass('not-favorite'); 151 link.addClass('favorite'); 152 link.attr('onclick', 'notifier.actions.removeFavorite(\''+noteId+'\', \''+fav.id+'\');'); 153 } 154 }); 155 } 156 notifier.actions.removeFavorite = function(noteId, favId) { 157 notifier.api.doRequest('users/'+notifier.prefs.username+'/favorites/'+favId+'.json', { 158 method: 'DELETE', 159 success: function() { 160 var link = $('#note_'+noteId+' a:first'); 161 link.removeClass('favorite'); 162 link.addClass('not-favorite'); 163 link.attr('onclick', 'notifier.actions.addFavorite(\''+noteId+'\');'); 164 } 165 }); 166 } 167 notifier.actions.refreshFavoritesList = function() { 168 notifier.api.doRequest('users/'+notifier.prefs.username+'/favorites.json', { 169 success: function(favs) { 170 $('#favorites-list').empty(); 171 if (favs.length == 0) { 172 $('#favorites-list').append('<p class="empty">Aucun favori pour l\'instant</p>'); 173 } else { 174 for (var i in favs) { 175 $('#favorites-list').append(notifier.ui.htmlForNote(favs[i])); 176 } 82 177 } 83 178 } … … 100 195 +'<p class="note-details">'+note.sender.name+' le '+note.timestamp 101 196 if (note.sender.name == notifier.prefs.username) { 197 html+= ' <a href="#" onclick="notifier.ui.createEditForm(\''+note.id+'\');">edit</a>'; 102 198 html+= ' <a href="#" onclick="notifier.actions.deleteNote(\''+note.id+'\');">delete</a>'; 199 } else { 200 if (note.favorite_id !== null) { 201 var className = 'favorite'; 202 var onclick = 'notifier.actions.removeFavorite(\''+note.id+'\', \''+note.favorite_id+'\');'; 203 } else { 204 var className = 'not-favorite'; 205 var onclick = 'notifier.actions.addFavorite(\''+note.id+'\');'; 206 } 207 html+= ' <a href="#" class="'+className+'" onclick="'+onclick+'"></a>'; 103 208 } 104 209 html+= '</p></div>'; … … 109 214 } 110 215 notifier.ui.htmlForFriend = function(friend) { 111 return '<div class="friend" >'216 return '<div class="friend" id="friend_'+friend.id+'">' 112 217 +'<span class="friend-avatar">?</span>' 113 218 +'<p class="friend-name">'+friend.name+'</p>' 114 +'<p class="friend-email">'+friend.email+'</p>' 219 +'<p class="friend-email">'+friend.email 220 +' <a href="#" onclick="notifier.actions.removeFriend(\''+friend.id+'\');">remove</a>' 221 +'</p>' 115 222 +'</div>' 223 } 224 notifier.ui.createEditForm = function(noteId) { 225 var form = document.createElement("form"); 226 form.id = 'edit_note_'+noteId; 227 form.className = 'edit-form'; 228 form.onsubmit = function() { notifier.actions.editNote(noteId); return false; } 229 230 var textArea = document.createElement("textarea"); 231 textArea.id = 'note_'+noteId+'_new_text'; 232 textArea.value = $('#note_'+noteId+' p:first').text(); 233 textArea.rows = 3; 234 textArea.cols = 40; 235 form.appendChild(textArea); 236 237 var br = document.createElement("br"); 238 form.appendChild(br); 239 240 var okButton = document.createElement("input"); 241 okButton.type = "submit"; 242 okButton.value = "Ok"; 243 form.appendChild(okButton); 244 245 var cancelLink = document.createElement("a"); 246 cancelLink.href = "#"; 247 cancelLink.appendChild(document.createTextNode("annuler")); 248 cancelLink.onclick = function() { notifier.ui.hideEditForm(noteId); } 249 form.appendChild(cancelLink); 250 251 $('#note_'+noteId).after(form); 252 $('#note_'+noteId).hide(); 253 } 254 notifier.ui.hideEditForm = function(noteId) { 255 $('#note_'+noteId).show(); 256 $('#edit_note_'+noteId).remove(); 116 257 } 117 258 … … 137 278 notifier.ui.toggleLoading(); 138 279 if (options.authenticate) { 139 xhr.setRequestHeader( "Authorization",140 "Basic " + Base64.encode(notifier.prefs.username + ":" +notifier.prefs.password));141 xhr.setRequestHeader("Cookie", "");280 xhr.setRequestHeader('Authorization', 'WSSE profile="UsernameToken"'); 281 xhr.setRequestHeader('X-WSSE', wsseHeader(notifier.prefs.username, notifier.prefs.password)); 282 //xhr.setRequestHeader("Cookie", ""); 142 283 } 143 284 return xhr; … … 173 314 notifier.api.alertError = function(error, status) { 174 315 var str = "Erreur "+status+" !\n\n"; 175 for (var key in error) { 176 str += key+': '+error[key]+"\n"; 316 if ('object' == typeof error) { 317 for (var key in error) { 318 str += key+': '+error[key]+"\n"; 319 } 320 } else { 321 str+= error; 177 322 } 178 323 alert(str); apps/notifier/public/styles/main.css
r894 r899 9 9 text-align: center; 10 10 } 11 h1 { 12 margin-bottom: 20px; 13 } 14 h2 { 15 margin-bottom: 10px; 16 } 11 17 label { 12 18 display: block; … … 14 20 textarea { 15 21 width: 100%; 22 margin-bottom: 10px; 16 23 } 17 input[type='text'], input[type='password'] {24 input[type='text'], input[type='password'], input[type='submit'] { 18 25 padding: 5px; 19 26 display: block; 20 27 margin-bottom: 10px; 28 } 29 input[type='submit'], input#friend_name { 30 display: inline; 31 margin-bottom: 0; 21 32 } 22 33 #container { … … 40 51 text-decoration: none; 41 52 border: 1px solid #aaa; 53 background-color: #ccc; 42 54 } 43 a.active { 44 background-color: yellow; 55 #tabs a.active { 56 background-color: white; 57 z-index: 10; 45 58 border-bottom: 1px solid white; 46 59 } … … 49 62 padding: 15px; 50 63 } 51 52 div.note, div.friend { 64 p.refresh-link { 65 text-align: right; 66 } 67 p.empty { 68 font-size: 16px; 69 padding: 10px 0; 70 } 71 form.edit-form { 72 padding-top: 10px; 73 text-align: right; 74 } 75 form.edit-form a { 76 margin-left: 5px; 77 } 78 div.note, div.friend, form.edit-form, p.empty { 53 79 border-top: 1px solid #ccc; 80 margin-top: 10px; 54 81 } 55 82 p.note-text { 56 83 border: 1px solid black; 57 84 background-color: #e6f2fe; 58 margin: 5px 0 00;85 margin: 10px 0 5px 0; 59 86 font-size: 16px; 60 87 padding: 5px; 61 88 } 62 89 p.note-details { 63 font-size: 1 1px;90 font-size: 12px; 64 91 text-align: right; 65 92 color: #bbb; … … 83 110 color: #bbb; 84 111 } 112 a.not-favorite { 113 background: transparent url(../images/not_fav.png) no-repeat 0 0; 114 padding: 0 7px; 115 } 116 a.favorite { 117 background: transparent url(../images/fav.png) no-repeat 0 0; 118 padding: 0 7px; 119 }
