mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
184 lines
5.8 KiB
JavaScript
184 lines
5.8 KiB
JavaScript
// All Tomorrow's Parties -- data model
|
|
// Loaded on both the client and the server
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Parties
|
|
|
|
/*
|
|
Each party is represented by a document in the Parties collection:
|
|
owner: user id
|
|
x, y: Number (screen coordinates in the interval [0, 1])
|
|
title, description: String
|
|
public: Boolean
|
|
invited: Array of user id's that are invited (only if !public)
|
|
rsvps: Array of objects like {user: userId, rsvp: "yes"} (or "no"/"maybe")
|
|
*/
|
|
Parties = new Mongo.Collection("parties");
|
|
|
|
Parties.allow({
|
|
insert: function (userId, party) {
|
|
return false; // no cowboy inserts -- use createParty method
|
|
},
|
|
update: function (userId, party, fields, modifier) {
|
|
if (userId !== party.owner)
|
|
return false; // not the owner
|
|
|
|
var allowed = ["title", "description", "x", "y"];
|
|
if (_.difference(fields, allowed).length)
|
|
return false; // tried to write to forbidden field
|
|
|
|
// A good improvement would be to validate the type of the new
|
|
// value of the field (and if a string, the length.) In the
|
|
// future Meteor will have a schema system to makes that easier.
|
|
return true;
|
|
},
|
|
remove: function (userId, party) {
|
|
// You can only remove parties that you created and nobody is going to.
|
|
return party.owner === userId && attending(party) === 0;
|
|
}
|
|
});
|
|
|
|
attending = function (party) {
|
|
return (_.groupBy(party.rsvps, 'rsvp').yes || []).length;
|
|
};
|
|
|
|
var NonEmptyString = Match.Where(function (x) {
|
|
check(x, String);
|
|
return x.length !== 0;
|
|
});
|
|
|
|
var Coordinate = Match.Where(function (x) {
|
|
check(x, Number);
|
|
return x >= 0 && x <= 1;
|
|
});
|
|
|
|
createParty = function (options) {
|
|
var id = Random.id();
|
|
Meteor.call('createParty', _.extend({ _id: id }, options));
|
|
return id;
|
|
};
|
|
|
|
Meteor.methods({
|
|
// options should include: title, description, x, y, public
|
|
createParty: function (options) {
|
|
check(options, {
|
|
title: NonEmptyString,
|
|
description: NonEmptyString,
|
|
x: Coordinate,
|
|
y: Coordinate,
|
|
public: Match.Optional(Boolean),
|
|
_id: Match.Optional(NonEmptyString)
|
|
});
|
|
|
|
if (options.title.length > 100)
|
|
throw new Meteor.Error(413, "Title too long");
|
|
if (options.description.length > 1000)
|
|
throw new Meteor.Error(413, "Description too long");
|
|
if (! this.userId)
|
|
throw new Meteor.Error(403, "You must be logged in");
|
|
|
|
var id = options._id || Random.id();
|
|
Parties.insert({
|
|
_id: id,
|
|
owner: this.userId,
|
|
x: options.x,
|
|
y: options.y,
|
|
title: options.title,
|
|
description: options.description,
|
|
public: !! options.public,
|
|
invited: [],
|
|
rsvps: []
|
|
});
|
|
return id;
|
|
},
|
|
|
|
invite: function (partyId, userId) {
|
|
check(partyId, String);
|
|
check(userId, String);
|
|
var party = Parties.findOne(partyId);
|
|
if (! party || party.owner !== this.userId)
|
|
throw new Meteor.Error(404, "No such party");
|
|
if (party.public)
|
|
throw new Meteor.Error(400,
|
|
"That party is public. No need to invite people.");
|
|
if (userId !== party.owner && ! _.contains(party.invited, userId)) {
|
|
Parties.update(partyId, { $addToSet: { invited: userId } });
|
|
|
|
var from = contactEmail(Meteor.users.findOne(this.userId));
|
|
var to = contactEmail(Meteor.users.findOne(userId));
|
|
if (Meteor.isServer && to) {
|
|
// This code only runs on the server. If you didn't want clients
|
|
// to be able to see it, you could move it to a separate file.
|
|
Email.send({
|
|
from: "noreply@example.com",
|
|
to: to,
|
|
replyTo: from || undefined,
|
|
subject: "PARTY: " + party.title,
|
|
text:
|
|
"Hey, I just invited you to '" + party.title + "' on All Tomorrow's Parties." +
|
|
"\n\nCome check it out: " + Meteor.absoluteUrl() + "\n"
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
rsvp: function (partyId, rsvp) {
|
|
check(partyId, String);
|
|
check(rsvp, String);
|
|
if (! this.userId)
|
|
throw new Meteor.Error(403, "You must be logged in to RSVP");
|
|
if (! _.contains(['yes', 'no', 'maybe'], rsvp))
|
|
throw new Meteor.Error(400, "Invalid RSVP");
|
|
var party = Parties.findOne(partyId);
|
|
if (! party)
|
|
throw new Meteor.Error(404, "No such party");
|
|
if (! party.public && party.owner !== this.userId &&
|
|
!_.contains(party.invited, this.userId))
|
|
// private, but let's not tell this to the user
|
|
throw new Meteor.Error(403, "No such party");
|
|
|
|
var rsvpIndex = _.indexOf(_.pluck(party.rsvps, 'user'), this.userId);
|
|
if (rsvpIndex !== -1) {
|
|
// update existing rsvp entry
|
|
|
|
if (Meteor.isServer) {
|
|
// update the appropriate rsvp entry with $
|
|
Parties.update(
|
|
{_id: partyId, "rsvps.user": this.userId},
|
|
{$set: {"rsvps.$.rsvp": rsvp}});
|
|
} else {
|
|
// minimongo doesn't yet support $ in modifier. as a temporary
|
|
// workaround, make a modifier that uses an index. this is
|
|
// safe on the client since there's only one thread.
|
|
var modifier = {$set: {}};
|
|
modifier.$set["rsvps." + rsvpIndex + ".rsvp"] = rsvp;
|
|
Parties.update(partyId, modifier);
|
|
}
|
|
|
|
// Possible improvement: send email to the other people that are
|
|
// coming to the party.
|
|
} else {
|
|
// add new rsvp entry
|
|
Parties.update(partyId,
|
|
{$push: {rsvps: {user: this.userId, rsvp: rsvp}}});
|
|
}
|
|
}
|
|
});
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Users
|
|
|
|
displayName = function (user) {
|
|
if (user.profile && user.profile.name)
|
|
return user.profile.name;
|
|
return user.emails[0].address;
|
|
};
|
|
|
|
var contactEmail = function (user) {
|
|
if (user.emails && user.emails.length)
|
|
return user.emails[0].address;
|
|
if (user.services && user.services.facebook && user.services.facebook.email)
|
|
return user.services.facebook.email;
|
|
return null;
|
|
};
|