Skip to content

Commit 416bb83

Browse files
committed
more work on courses
1 parent 74bfe8d commit 416bb83

20 files changed

Lines changed: 462 additions & 41 deletions

File tree

fixture/init/course.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ exports.Course = [
77
{
88
"_id": "543250000000000000000002",
99
slug: "js",
10+
videoKeyTag: "js",
1011
title: "Курс JavaScript/DOM/интерфейсы",
12+
isListed: true,
1113
weight: 1
1214
}
1315
];
@@ -16,24 +18,28 @@ exports.Course = [
1618
exports.CourseGroup = [
1719
{
1820
course: '543250000000000000000002',
19-
dateStart: new Date(2015, 0, 1),
20-
dateEnd: new Date(2015, 10, 10),
21+
dateStart: new Date(2016, 0, 1),
22+
dateEnd: new Date(2016, 10, 10),
2123
timeDesc: "пн/чт 19:30 - 21:00 GMT+3",
2224
slug: 'js-1',
2325
price: 1,
2426
participantsLimit: 30,
2527
webinarId: '123',
28+
isListed: true,
29+
isOpenForSignup: true,
2630
title: "Курс JavaScript/DOM/интерфейсы (01.01)"
2731
},
2832
{
2933
course: '543250000000000000000002',
30-
dateStart: new Date(2015, 5, 1),
31-
dateEnd: new Date(2015, 11, 10),
34+
dateStart: new Date(2016, 5, 1),
35+
dateEnd: new Date(2016, 11, 10),
3236
timeDesc: "пн/чт 21:30 - 23:00 GMT+3",
3337
slug: 'js-2',
3438
price: 1,
3539
webinarId: '456',
3640
participantsLimit: 30,
41+
isListed: true,
42+
isOpenForSignup: true,
3743
title: "Курс JavaScript/DOM/интерфейсы (06.01)"
3844
}
3945
];

gulpfile.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ gulp.task("client:sync-resources", lazyRequireTask('./tasks/syncResources', {
115115
assets: 'public'
116116
}));
117117

118+
gulp.task("videoKey:load", lazyRequireTask('videoKey/tasks/load'));
119+
118120
// Show errors if encountered
119121
gulp.task('client:compile-css',
120122
lazyRequireTask('./tasks/compileCss', {

handlers/courses/controller/course.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ exports.get = function*() {
1212
}
1313

1414
this.locals.groups = yield CourseGroup.find({
15+
isListed: true,
16+
dateStart: {
17+
$gt: new Date()
18+
},
1519
course: this.locals.course._id
1620
}).sort({
1721
dateStart: 1

handlers/courses/controller/coursesByUser.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ function formatGroup(group) {
4747
return {
4848
title: group.title,
4949
groupUrl: group.getUrl(),
50-
groupPrivateUrl: group.getPrivateUrl(),
50+
groupPrivateUrl: TODO,
5151
dateStart: group.dateStart,
5252
dateEnd: group.dateEnd,
5353
timeDesc: group.timeDesc

handlers/courses/controller/frontpage.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ var Course = require('../models/course');
22

33
exports.get = function*() {
44

5-
this.locals.courses = yield Course.find({}).sort({weight: 1}).exec();
5+
this.locals.courses = yield Course.find({
6+
isListed: true
7+
}).sort({weight: 1}).exec();
68

79
this.body = this.render('frontpage');
810
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
var Course = require('../models/course');
2+
var CourseGroup = require('../models/courseGroup');
3+
var _ = require('lodash');
4+
5+
// Group info for a participant, with user instructions on how to login
6+
// TODO
7+
exports.get = function*() {
8+
9+
var group = this.locals.group = yield CourseGroup.findOne({
10+
slug: this.params.group
11+
}).populate('course').exec();
12+
13+
if (!group) {
14+
this.throw(404, "Нет такой группы.");
15+
}
16+
17+
if (!this.user) {
18+
this.throw(401);
19+
}
20+
21+
var participantIds = _.pluck(group.participants, 'user').map(String);
22+
if (!~participantIds.indexOf(this.user._id)) {
23+
this.throw(403, "Вы не являетесь участником этой группы.");
24+
}
25+
26+
this.body = this.render('groupInfo/' + group.course.slug);
27+
};

handlers/courses/controller/signup.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ exports.get = function*() {
4141
this.throw(404, "Нет такой группы.");
4242
}
4343

44+
// a visitor can't reach this page through UI, only by direct link
45+
// if the group is full
46+
if (!group.isOpenForSignup) {
47+
this.throw(403, "Запись в эту группу завершена.");
48+
}
49+
4450
if (!this.isAuthenticated()) {
4551
this.redirect(group.course.getUrl());
4652
return;

handlers/courses/lib/createOrderFromTemplate.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ module.exports = function*(orderTemplate, user, requestBody) {
2121
}
2222

2323
if (orderData.count > group.participantsLimit) {
24-
throw new OrderCreateError("Извините, уже нет столько мест. Уменьшите количество участников до " + group.participantsLimit + '.');
24+
throw new OrderCreateError("Извините, уже нет такого количества мест. Уменьшите количество участников до " + group.participantsLimit + '.');
2525
}
2626

2727
orderData.contactName = String(requestBody.contactName);

handlers/courses/lib/onPaid.js

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const CourseInvite = require('../models/courseInvite');
77
const CourseGroup = require('../models/courseGroup');
88
const sendOrderInvites = require('./sendOrderInvites');
99
const xmppClient = require('xmppClient');
10+
const VideoKey = require('videoKey').VideoKey;
1011

1112
// not a middleware
1213
// can be called from CRON
@@ -40,11 +41,35 @@ module.exports = function* (order) {
4041
user: order.user._id,
4142
courseName: order.data.contactName
4243
});
43-
yield group.persist();
44+
group.participantsLimit--;
4445
}
46+
if (group.participantsLimit < 0) group.participantsLimit = 0;
47+
if (group.participantsLimit === 0) {
48+
group.isOpenForSignup = false; // we're full!
49+
}
50+
yield group.persist();
4551

4652
yield* sendOrderInvites(order);
4753

54+
yield CourseGroup.populate(group,[{path: 'participants.user'}, {path: 'course'}]);
55+
56+
yield* grantXmppChatMemberships(group);
57+
58+
if (group.course.videoKeyTag) {
59+
yield *grantVideoKeys(group);
60+
}
61+
62+
63+
order.status = Order.STATUS_SUCCESS;
64+
65+
yield order.persist();
66+
67+
log.debug("Order success: " + order.number);
68+
};
69+
70+
71+
function* grantXmppChatMemberships(group) {
72+
log.debug("Grant xmpp chat membership");
4873
// grant membership in chat
4974
var client = new xmppClient({
5075
jid: config.xmpp.admin.login + '/host',
@@ -58,18 +83,50 @@ module.exports = function* (order) {
5883
membersOnly: 1
5984
});
6085

61-
yield CourseGroup.populate(group, {path: 'participants.user'});
6286

87+
var jobs = [];
6388
for (var i = 0; i < group.participants.length; i++) {
6489
var participant = group.participants[i];
65-
yield client.grantMember(roomJid, participant.user.profileName, participant.courseName);
90+
91+
log.debug("grant " + roomJid + " to", participant.user.profileName, participant.courseName);
92+
93+
jobs.push(client.grantMember(roomJid, participant.user.profileName, participant.courseName));
6694
}
6795

96+
// grant all in parallel
97+
yield jobs;
98+
6899
client.disconnect();
100+
}
69101

70-
order.status = Order.STATUS_SUCCESS;
102+
function* grantVideoKeys(group) {
71103

72-
yield order.persist();
104+
var participants = group.participants.filter(function(participant) {
105+
return !participant.videoKey;
106+
});
73107

74-
log.debug("Order success: " + order.number);
75-
};
108+
109+
var videoKeys = yield VideoKey.find({
110+
tag: group.course.videoKeyTag,
111+
used: false
112+
}).limit(participants.length).exec();
113+
114+
log.debug("Keys selected", videoKeys && videoKeys.toArray());
115+
116+
if (!videoKeys || videoKeys.length != participants.length) {
117+
throw new Error("Недостаточно серийных номеров " + participants.length);
118+
}
119+
120+
for (var i = 0; i < participants.length; i++) {
121+
var participant = participants[i];
122+
participant.videoKey = videoKeys[i].key;
123+
videoKeys[i].used = true;
124+
}
125+
126+
yield group.persist();
127+
128+
var jobs = videoKeys.map(function(videoKey) {
129+
return videoKey.persist();
130+
});
131+
yield jobs;
132+
}

handlers/courses/models/course.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
var mongoose = require('mongoose');
22
var Schema = mongoose.Schema;
33

4-
// the schema follows http://openexchangerates.org/api/latest.json response
54
var schema = new Schema({
65
// like "nodejs", same as template
76
slug: {
@@ -16,11 +15,25 @@ var schema = new Schema({
1615
required: true
1716
},
1817

18+
videoKeyTag: {
19+
// may be 2 adjacent courses have same video tag
20+
type: String
21+
},
22+
1923
weight: {
2024
type: Number,
2125
required: true
2226
},
2327

28+
// is this course in the open course list (otherwise hidden)?
29+
// even if not, the course is accessible by a direct link
30+
isListed: {
31+
type: Boolean,
32+
required: true,
33+
default: false
34+
},
35+
36+
2437
created: {
2538
type: Date,
2639
default: Date.now

0 commit comments

Comments
 (0)