Skip to content

Commit 820f5ae

Browse files
committed
v2: Move to unique username property + constraint.
1 parent c41593e commit 820f5ae

File tree

6 files changed

+107
-97
lines changed

6 files changed

+107
-97
lines changed

app.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@ app.get('/', routes.site.index);
3636

3737
app.get('/users', routes.users.list);
3838
app.post('/users', routes.users.create);
39-
app.get('/users/:id', routes.users.show);
40-
app.post('/users/:id', routes.users.edit);
41-
app.del('/users/:id', routes.users.del);
39+
app.get('/users/:username', routes.users.show);
40+
app.post('/users/:username', routes.users.edit);
41+
app.del('/users/:username', routes.users.del);
4242

43-
app.post('/users/:id/follow', routes.users.follow);
44-
app.post('/users/:id/unfollow', routes.users.unfollow);
43+
app.post('/users/:username/follow', routes.users.follow);
44+
app.post('/users/:username/unfollow', routes.users.unfollow);
4545

4646
http.createServer(app).listen(app.get('port'), function(){
4747
console.log('Express server listening at: http://localhost:%d/', app.get('port'));

models/user.js

Lines changed: 45 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,9 @@ var User = module.exports = function User(_node) {
2121

2222
// Public instance properties:
2323

24-
// TODO: Using native Neo4j IDs is now discouraged; switch to an indexed+unique
25-
// property instead, e.g. `email` or `username` or etc.
26-
Object.defineProperty(User.prototype, 'id', {
27-
get: function () { return this._node._id; }
28-
});
29-
30-
Object.defineProperty(User.prototype, 'name', {
31-
get: function () { return this._node.properties['name']; }
24+
// The user's username, e.g. 'aseemk'.
25+
Object.defineProperty(User.prototype, 'username', {
26+
get: function () { return this._node.properties['username']; }
3227
});
3328

3429
// Private helpers:
@@ -37,10 +32,10 @@ Object.defineProperty(User.prototype, 'name', {
3732
// instance properties), selects only whitelisted ones for editing, validates
3833
// them, and translates them to the corresponding internal db properties.
3934
function translate(props) {
40-
// Today, the only property we have is `name`; it's the same; and it needs
41-
// no validation. (Might want to validate things like length, Unicode, etc.)
35+
// Today, the only property we have is `username`.
36+
// TODO: Validate it. E.g. length, acceptable chars, etc.
4237
return {
43-
name: props.name,
38+
username: props.username,
4439
};
4540
}
4641

@@ -52,14 +47,13 @@ User.prototype.patch = function (props, callback) {
5247
var safeProps = translate(props);
5348

5449
var query = [
55-
'MATCH (user:User)',
56-
'WHERE ID(user) = {id}',
50+
'MATCH (user:User {username: {username}})',
5751
'SET user += {props}',
5852
'RETURN user',
5953
].join('\n');
6054

6155
var params = {
62-
id: this.id,
56+
username: this.username,
6357
props: safeProps,
6458
};
6559

@@ -72,7 +66,7 @@ User.prototype.patch = function (props, callback) {
7266
if (err) return callback(err);
7367

7468
if (!results.length) {
75-
err = new Error('User has been deleted! ID: ' + self.id);
69+
err = new Error('User has been deleted! Username: ' + self.username);
7670
return callback(err);
7771
}
7872

@@ -89,14 +83,13 @@ User.prototype.del = function (callback) {
8983
// (Note that this'll still fail if there are any relationships attached
9084
// of any other types, which is good because we don't expect any.)
9185
var query = [
92-
'MATCH (user:User)',
93-
'WHERE ID(user) = {userId}',
86+
'MATCH (user:User {username: {username}})',
9487
'OPTIONAL MATCH (user) -[rel:follows]- (other)',
9588
'DELETE user, rel',
9689
].join('\n')
9790

9891
var params = {
99-
userId: this.id
92+
username: this.username,
10093
};
10194

10295
db.cypher({
@@ -109,14 +102,14 @@ User.prototype.del = function (callback) {
109102

110103
User.prototype.follow = function (other, callback) {
111104
var query = [
112-
'MATCH (user:User) ,(other:User)',
113-
'WHERE ID(user) = {userId} AND ID(other) = {otherId}',
105+
'MATCH (user:User {username: {thisUsername}})',
106+
'MATCH (other:User {username: {otherUsername}})',
114107
'MERGE (user) -[rel:follows]-> (other)',
115108
].join('\n')
116109

117110
var params = {
118-
userId: this.id,
119-
otherId: other.id,
111+
thisUsername: this.username,
112+
otherUsername: other.username,
120113
};
121114

122115
db.cypher({
@@ -129,14 +122,15 @@ User.prototype.follow = function (other, callback) {
129122

130123
User.prototype.unfollow = function (other, callback) {
131124
var query = [
132-
'MATCH (user:User) -[rel:follows]-> (other:User)',
133-
'WHERE ID(user) = {userId} AND ID(other) = {otherId}',
125+
'MATCH (user:User {username: {thisUsername}})',
126+
'MATCH (other:User {username: {otherUsername}})',
127+
'MATCH (user) -[rel:follows]-> (other)',
134128
'DELETE rel',
135129
].join('\n')
136130

137131
var params = {
138-
userId: this.id,
139-
otherId: other.id,
132+
thisUsername: this.username,
133+
otherUsername: other.username,
140134
};
141135

142136
db.cypher({
@@ -152,14 +146,14 @@ User.prototype.unfollow = function (other, callback) {
152146
User.prototype.getFollowingAndOthers = function (callback) {
153147
// Query all users and whether we follow each one or not:
154148
var query = [
155-
'MATCH (user:User), (other:User)',
149+
'MATCH (user:User {username: {thisUsername}})',
150+
'MATCH (other:User)',
156151
'OPTIONAL MATCH (user) -[rel:follows]-> (other)',
157-
'WHERE ID(user) = {userId}',
158152
'RETURN other, COUNT(rel)', // COUNT(rel) is a hack for 1 or 0
159153
].join('\n')
160154

161155
var params = {
162-
userId: this.id,
156+
thisUsername: this.username,
163157
};
164158

165159
var user = this;
@@ -176,7 +170,7 @@ User.prototype.getFollowingAndOthers = function (callback) {
176170
var other = new User(results[i]['other']);
177171
var follows = results[i]['COUNT(rel)'];
178172

179-
if (user.id === other.id) {
173+
if (user.username === other.username) {
180174
continue;
181175
} else if (follows) {
182176
following.push(other);
@@ -191,15 +185,14 @@ User.prototype.getFollowingAndOthers = function (callback) {
191185

192186
// Static methods:
193187

194-
User.get = function (id, callback) {
188+
User.get = function (username, callback) {
195189
var query = [
196-
'MATCH (user:User)',
197-
'WHERE ID(user) = {id}',
190+
'MATCH (user:User {username: {username}})',
198191
'RETURN user',
199192
].join('\n')
200193

201194
var params = {
202-
id: parseInt(id, 10),
195+
username: username,
203196
};
204197

205198
db.cypher({
@@ -208,7 +201,7 @@ User.get = function (id, callback) {
208201
}, function (err, results) {
209202
if (err) return callback(err);
210203
if (!results.length) {
211-
err = new Error('No such user with ID: ' + id);
204+
err = new Error('No such user with username: ' + username);
212205
return callback(err);
213206
}
214207
var user = new User(results[0]['user']);
@@ -253,3 +246,20 @@ User.create = function (props, callback) {
253246
callback(null, user);
254247
});
255248
};
249+
250+
// Static initialization:
251+
252+
// Register our unique username constraint.
253+
// TODO: This is done async'ly (fire and forget) here for simplicity,
254+
// but this would be better as a formal schema migration script or similar.
255+
db.createConstraint({
256+
label: 'User',
257+
property: 'username',
258+
}, function (err, constraint) {
259+
if (err) throw err; // Failing fast for now, by crash the application.
260+
if (constraint) {
261+
console.log('(Registered unique usernames constraint.)');
262+
} else {
263+
// Constraint already present; no need to log anything.
264+
}
265+
})

routes/users.js

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,18 @@ exports.list = function (req, res, next) {
2020
*/
2121
exports.create = function (req, res, next) {
2222
User.create({
23-
name: req.body['name']
23+
username: req.body['username']
2424
}, function (err, user) {
2525
if (err) return next(err);
26-
res.redirect('/users/' + user.id);
26+
res.redirect('/users/' + user.username);
2727
});
2828
};
2929

3030
/**
31-
* GET /users/:id
31+
* GET /users/:username
3232
*/
3333
exports.show = function (req, res, next) {
34-
User.get(req.params.id, function (err, user) {
34+
User.get(req.params.username, function (err, user) {
3535
if (err) return next(err);
3636
// TODO: Also fetch and show followers? (Not just follow*ing*.)
3737
user.getFollowingAndOthers(function (err, following, others) {
@@ -46,23 +46,23 @@ exports.show = function (req, res, next) {
4646
};
4747

4848
/**
49-
* POST /users/:id
49+
* POST /users/:username
5050
*/
5151
exports.edit = function (req, res, next) {
52-
User.get(req.params.id, function (err, user) {
52+
User.get(req.params.username, function (err, user) {
5353
if (err) return next(err);
5454
user.patch(req.body, function (err) {
5555
if (err) return next(err);
56-
res.redirect('/users/' + user.id);
56+
res.redirect('/users/' + user.username);
5757
});
5858
});
5959
};
6060

6161
/**
62-
* DELETE /users/:id
62+
* DELETE /users/:username
6363
*/
6464
exports.del = function (req, res, next) {
65-
User.get(req.params.id, function (err, user) {
65+
User.get(req.params.username, function (err, user) {
6666
if (err) return next(err);
6767
user.del(function (err) {
6868
if (err) return next(err);
@@ -72,32 +72,32 @@ exports.del = function (req, res, next) {
7272
};
7373

7474
/**
75-
* POST /users/:id/follow
75+
* POST /users/:username/follow
7676
*/
7777
exports.follow = function (req, res, next) {
78-
User.get(req.params.id, function (err, user) {
78+
User.get(req.params.username, function (err, user) {
7979
if (err) return next(err);
80-
User.get(req.body.user.id, function (err, other) {
80+
User.get(req.body.otherUsername, function (err, other) {
8181
if (err) return next(err);
8282
user.follow(other, function (err) {
8383
if (err) return next(err);
84-
res.redirect('/users/' + user.id);
84+
res.redirect('/users/' + user.username);
8585
});
8686
});
8787
});
8888
};
8989

9090
/**
91-
* POST /users/:id/unfollow
91+
* POST /users/:username/unfollow
9292
*/
9393
exports.unfollow = function (req, res, next) {
94-
User.get(req.params.id, function (err, user) {
94+
User.get(req.params.username, function (err, user) {
9595
if (err) return next(err);
96-
User.get(req.body.user.id, function (err, other) {
96+
User.get(req.body.otherUsername, function (err, other) {
9797
if (err) return next(err);
9898
user.unfollow(other, function (err) {
9999
if (err) return next(err);
100-
res.redirect('/users/' + user.id);
100+
res.redirect('/users/' + user.username);
101101
});
102102
});
103103
});

0 commit comments

Comments
 (0)