Changeset 899

Show
Ignore:
Timestamp:
02/29/08 09:56:24
Author:
goldoraf
Message:

Finished notifier demo app

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • apps/notifier/app/models/status.php

    r894 r899  
    66    { 
    77        return $this->by_sql( 
    8             "SELECT statuses.* FROM statuses 
     8            "SELECT statuses.*, favorites.id AS favorite_id FROM statuses 
    99            LEFT OUTER JOIN users ON users.id = statuses.sender_id  
    1010            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) 
    1112            WHERE friendships.user_id = '{$user_id}' OR statuses.sender_id = '{$user_id}' 
    1213            ORDER BY timestamp DESC LIMIT {$limit}" 
     
    2223    ); 
    2324     
     25    public function validate() 
     26    { 
     27        $this->validate_presence_of(array('text', 'sender_id')); 
     28    } 
     29     
    2430    public function serializable_form() 
    2531    { 
    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; 
    2737    } 
    2838} 
  • apps/notifier/app/models/user.php

    r894 r899  
    55    public static $objects; 
    66    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'), 
    711        'friends' => array('assoc_type' => 'many_to_many', 'join_table' => 'friendships', 
    8                            'class_name' => 'User' 
     12                           'class_name' => 'User', 'association_foreign_key' => 'friend_id') 
    913    ); 
    1014     
    11     public static function authenticate($login, $password
     15    public static function authenticate($login
    1216    { 
    1317        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); 
    1520        } catch (SRecordNotFound $e) { 
    1621            return false; 
     
    2126    { 
    2227        $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')); 
    2533    } 
    2634     
  • apps/notifier/app/resources/base_resource.php

    r894 r899  
    11<?php 
    22 
    3 require STATO_CORE_PATH.'/components/http_authentication/lib/basic_http_authentication.php'; 
     3require STATO_CORE_PATH.'/components/http_authentication/lib/wsse_http_authentication.php'; 
    44 
    55class BaseResource extends SResource 
     
    88    protected $user; 
    99     
     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     
    1018    protected function authenticate() 
    1119    { 
    1220        $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; 
    1525    } 
    1626     
  • apps/notifier/app/resources/friends_resource.php

    r894 r899  
    33class FriendsResource extends BaseResource 
    44{ 
    5     public function __construct() 
     5    public function initialize() 
    66    { 
    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')); 
    99    } 
    1010     
     
    1313        return $this->user->friends->all(); 
    1414    } 
     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    } 
    1530} 
    1631 
  • apps/notifier/app/resources/friends_timeline_resource.php

    r894 r899  
    33class FriendsTimelineResource extends BaseResource 
    44{ 
    5     public function __construct() 
     5    public function initialize() 
    66    { 
    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'); 
    98    } 
    109     
    1110    public function get() 
    1211    { 
    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); 
    1414    } 
    1515} 
  • apps/notifier/app/resources/statuses_resource.php

    r894 r899  
    33class StatusesResource extends BaseResource 
    44{ 
    5     public function __construct() 
     5    public function initialize() 
    66    { 
    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')); 
    119    } 
    1210     
    1311    public function get() 
    1412    { 
    15         return Status::$objects->get($this->params['id']); 
     13        $statuses = $this->user->statuses->limit(20)->order_by('-timestamp'); 
     14        $this->responds($statuses, 200); 
    1615    } 
    1716     
    1817    public function post() 
    1918    { 
    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        } 
    3130    } 
    3231} 
  • apps/notifier/app/resources/users_resource.php

    r894 r899  
    11<?php 
    22 
    3 class UsersResource extends BaseResource 
     3class UsersResource extends SResource 
    44{ 
    5     public function __construct() 
    6     { 
    7         $this->add_before_filter('authenticate', array('except' => 'post')); 
    8     } 
    9      
    105    public function post() 
    116    { 
     
    1510            $this->responds_error(409); 
    1611        } catch (SRecordNotFound $e) { 
    17             $this->params['user']['pwd'] = md5($this->params['user']['pwd']); 
    1812            $user = new User($this->params['user']); 
    1913            if ($user->save()) 
  • apps/notifier/conf/routes.php

    r894 r899  
    22 
    33$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')); 
    710$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')); 
    1214return $map; 
    1315 
  • apps/notifier/db/test_data.sql

    r894 r899  
    3737 
    3838INSERT 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  
    88RewriteBase /notifier 
    99 
    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] 
     10RewriteRule ^$ index.html [QSA] 
    1611RewriteCond %{REQUEST_FILENAME} !-f 
    1712RewriteRule ^(.*)$ index.php [QSA,L] 
  • apps/notifier/public/js/notifier.js

    r894 r899  
    88notifier.actions = {}; 
    99notifier.actions.login = function() { 
    10         notifier.prefs.username = $('#name').val(); 
     10        $('#login input:submit').attr('disabled', 'true'); 
     11    notifier.prefs.username = $('#name').val(); 
    1112    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    }); 
    1530} 
    1631notifier.actions.register = function() { 
     
    2540        params: data, 
    2641        success: function() { 
     42            $('#register input:not(:submit)').val(''); 
    2743            $('#register').hide(); 
    2844            $('#login').show(); 
     
    3854} 
    3955notifier.actions.refreshFriendsTimeline = function() { 
    40     notifier.api.doRequest('users/'+notifier.prefs.username+'/friends/timeline.json', { 
     56    notifier.api.doRequest('users/'+notifier.prefs.username+'/timeline.json', { 
    4157        success: function(notes) { 
    4258            $('#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                } 
    4565            } 
    4666        } 
     
    5676            notifier.ui.prependNote(note); 
    5777            $('#note_text').val(''); 
    58             $('#note_submit').attr('disabled', 'false'); 
     78            $('#note_submit').removeAttr('disabled'); 
    5979        }, 
    6080        error: function(error, status) { 
    61             $('#note_submit').attr('disabled', 'false'); 
     81            $('#note_submit').removeAttr('disabled'); 
    6282            notifier.api.alertError(error, status); 
     83        } 
     84    }); 
     85} 
     86notifier.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); 
    6393        } 
    6494    }); 
     
    78108        success: function(friends) { 
    79109            $('#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
     120notifier.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
     136notifier.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
     144notifier.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
     156notifier.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
     167notifier.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                } 
    82177            } 
    83178        } 
     
    100195             +'<p class="note-details">'+note.sender.name+' le '+note.timestamp 
    101196    if (note.sender.name == notifier.prefs.username) { 
     197        html+= '&nbsp;<a href="#" onclick="notifier.ui.createEditForm(\''+note.id+'\');">edit</a>'; 
    102198        html+= '&nbsp;<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+= '&nbsp;<a href="#" class="'+className+'" onclick="'+onclick+'"></a>'; 
    103208    } 
    104209    html+= '</p></div>'; 
     
    109214} 
    110215notifier.ui.htmlForFriend = function(friend) { 
    111     return '<div class="friend">' 
     216    return '<div class="friend" id="friend_'+friend.id+'">' 
    112217            +'<span class="friend-avatar">?</span>' 
    113218            +'<p class="friend-name">'+friend.name+'</p>' 
    114             +'<p class="friend-email">'+friend.email+'</p>' 
     219            +'<p class="friend-email">'+friend.email 
     220            +'&nbsp;<a href="#" onclick="notifier.actions.removeFriend(\''+friend.id+'\');">remove</a>' 
     221            +'</p>' 
    115222            +'</div>' 
     223} 
     224notifier.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} 
     254notifier.ui.hideEditForm = function(noteId) { 
     255    $('#note_'+noteId).show(); 
     256    $('#edit_note_'+noteId).remove(); 
    116257} 
    117258 
     
    137278            notifier.ui.toggleLoading(); 
    138279            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", ""); 
    142283            } 
    143284            return xhr; 
     
    173314notifier.api.alertError = function(error, status) { 
    174315    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; 
    177322    } 
    178323    alert(str); 
  • apps/notifier/public/styles/main.css

    r894 r899  
    99    text-align: center; 
    1010} 
     11h1 { 
     12    margin-bottom: 20px; 
     13} 
     14h2 { 
     15    margin-bottom: 10px; 
     16} 
    1117label { 
    1218        display: block; 
     
    1420textarea { 
    1521        width: 100%; 
     22    margin-bottom: 10px; 
    1623} 
    17 input[type='text'], input[type='password']
     24input[type='text'], input[type='password'], input[type='submit']
    1825        padding: 5px; 
    1926        display: block; 
    2027        margin-bottom: 10px; 
     28} 
     29input[type='submit'], input#friend_name { 
     30    display: inline; 
     31    margin-bottom: 0; 
    2132} 
    2233#container { 
     
    4051    text-decoration: none; 
    4152    border: 1px solid #aaa; 
     53    background-color: #ccc; 
    4254} 
    43 a.active { 
    44     background-color: yellow; 
     55#tabs a.active { 
     56    background-color: white; 
     57    z-index: 10; 
    4558    border-bottom: 1px solid white; 
    4659} 
     
    4962    padding: 15px; 
    5063} 
    51      
    52 div.note, div.friend { 
     64p.refresh-link { 
     65    text-align: right;    
     66
     67p.empty { 
     68    font-size: 16px; 
     69    padding: 10px 0; 
     70
     71form.edit-form { 
     72    padding-top: 10px; 
     73    text-align: right;    
     74
     75form.edit-form a { 
     76    margin-left: 5px; 
     77
     78div.note, div.friend, form.edit-form, p.empty { 
    5379        border-top: 1px solid #ccc; 
     80    margin-top: 10px; 
    5481} 
    5582p.note-text { 
    5683        border: 1px solid black; 
    5784        background-color: #e6f2fe; 
    58         margin: 5px 0 0 0; 
     85        margin: 10px 0 5px 0; 
    5986    font-size: 16px; 
    6087    padding: 5px; 
    6188} 
    6289p.note-details { 
    63         font-size: 11px; 
     90        font-size: 12px; 
    6491        text-align: right; 
    6592        color: #bbb; 
     
    83110    color: #bbb; 
    84111} 
     112a.not-favorite { 
     113    background: transparent url(../images/not_fav.png) no-repeat 0 0; 
     114    padding: 0 7px; 
     115} 
     116a.favorite { 
     117    background: transparent url(../images/fav.png) no-repeat 0 0; 
     118    padding: 0 7px; 
     119}