mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-12 00:31:21 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
2447e790e1
13 changed files with 111 additions and 42 deletions
|
@ -4,3 +4,8 @@ CocoCollection = require 'collections/CocoCollection'
|
|||
module.exports = class Courses extends CocoCollection
|
||||
model: Course
|
||||
url: '/db/course'
|
||||
|
||||
fetchReleased: (options = {}) ->
|
||||
options.data ?= {}
|
||||
options.data.releasePhase = 'released'
|
||||
@fetch(options)
|
||||
|
|
|
@ -11,7 +11,8 @@ _.extend CourseSchema.properties,
|
|||
pricePerSeat: {type: 'number', description: 'Price per seat in USD cents.'} # deprecated
|
||||
free: { type: 'boolean' }
|
||||
screenshot: c.url {title: 'URL', description: 'Link to course screenshot.'}
|
||||
adminOnly: {type: 'boolean', description: 'Whether the course is in admin-only testing mode still and will not show up for normal users.'}
|
||||
adminOnly: { type: 'boolean', description: 'Deprecated in favor of releasePhase.' }
|
||||
releasePhase: { type: {enum: ['beta', 'released'] }, description: "How far along the course's development is, determining who sees it." }
|
||||
|
||||
c.extendBasicProperties CourseSchema, 'Course'
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ template = require 'templates/new-home-view'
|
|||
CocoCollection = require 'collections/CocoCollection'
|
||||
TrialRequest = require 'models/TrialRequest'
|
||||
TrialRequests = require 'collections/TrialRequests'
|
||||
Course = require 'models/Course'
|
||||
Courses = require 'collections/Courses'
|
||||
utils = require 'core/utils'
|
||||
storage = require 'core/storage'
|
||||
{logoutUser, me} = require('core/auth')
|
||||
|
@ -36,8 +36,8 @@ module.exports = class NewHomeView extends RootView
|
|||
'esc': 'onEscapePressed'
|
||||
|
||||
initialize: (options) ->
|
||||
@courses = new CocoCollection [], {url: "/db/course", model: Course}
|
||||
@supermodel.loadCollection(@courses, 'courses')
|
||||
@courses = new Courses()
|
||||
@supermodel.trackRequest @courses.fetchReleased()
|
||||
|
||||
if me.isTeacher()
|
||||
@trialRequests = new TrialRequests()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
app = require 'core/application'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
CocoModel = require 'models/CocoModel'
|
||||
Course = require 'models/Course'
|
||||
Courses = require 'collections/Courses'
|
||||
Campaigns = require 'collections/Campaigns'
|
||||
Classroom = require 'models/Classroom'
|
||||
Classrooms = require 'collections/Classrooms'
|
||||
|
@ -40,8 +40,8 @@ module.exports = class TeacherCoursesView extends RootView
|
|||
@ownedClassrooms = new Classrooms()
|
||||
@ownedClassrooms.fetchMine({data: {project: '_id'}})
|
||||
@supermodel.trackCollection(@ownedClassrooms)
|
||||
@courses = new CocoCollection([], { url: "/db/course", model: Course})
|
||||
@supermodel.loadCollection(@courses, 'courses')
|
||||
@courses = new Courses()
|
||||
@supermodel.trackRequest @courses.fetchReleased()
|
||||
@campaigns = new Campaigns()
|
||||
@supermodel.trackRequest @campaigns.fetchByType('course', { data: { project: 'levels,levelsUpdated' } })
|
||||
@
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
// Usage:
|
||||
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
|
||||
// eg: mongo localhost:27017/coco scripts/mongodb/updateCourses.js
|
||||
|
||||
// NOTE: uses name as unique identifier, so changing the name will insert a new course
|
||||
// NOTE: pricePerSeat in USD cents
|
||||
|
@ -15,7 +16,8 @@ var courses =
|
|||
description: "Learn basic syntax, while loops, and the CodeCombat environment.",
|
||||
duration: NumberInt(1),
|
||||
free: true,
|
||||
screenshot: "/images/pages/courses/101_info.png"
|
||||
screenshot: "/images/pages/courses/101_info.png",
|
||||
releasePhase: 'released'
|
||||
},
|
||||
{
|
||||
name: "Computer Science 2",
|
||||
|
@ -24,7 +26,8 @@ var courses =
|
|||
description: "Introduce Arguments, Variables, If Statements, and Arithmetic.",
|
||||
duration: NumberInt(5),
|
||||
free: false,
|
||||
screenshot: "/images/pages/courses/102_info.png"
|
||||
screenshot: "/images/pages/courses/102_info.png",
|
||||
releasePhase: 'released'
|
||||
},
|
||||
{
|
||||
name: "Computer Science 3",
|
||||
|
@ -33,7 +36,8 @@ var courses =
|
|||
description: "Introduces arithmetic, counters, advanced while loops, break, continue, arrays.",
|
||||
duration: NumberInt(5),
|
||||
free: false,
|
||||
screenshot: "/images/pages/courses/103_info.png"
|
||||
screenshot: "/images/pages/courses/103_info.png",
|
||||
releasePhase: 'released'
|
||||
},
|
||||
{
|
||||
name: "Computer Science 4",
|
||||
|
@ -42,7 +46,8 @@ var courses =
|
|||
description: "Introduces object literals, for loops, function definitions, drawing, and modulo.",
|
||||
duration: NumberInt(5),
|
||||
free: false,
|
||||
screenshot: "/images/pages/courses/104_info.png"
|
||||
screenshot: "/images/pages/courses/104_info.png",
|
||||
releasePhase: 'released'
|
||||
},
|
||||
{
|
||||
name: "Computer Science 5",
|
||||
|
@ -51,7 +56,8 @@ var courses =
|
|||
description: "Introduces function parameters, function return values and algorithms.",
|
||||
duration: NumberInt(5),
|
||||
free: false,
|
||||
screenshot: "/images/pages/courses/105_info.png"
|
||||
screenshot: "/images/pages/courses/105_info.png",
|
||||
releasePhase: 'released'
|
||||
},
|
||||
{
|
||||
name: "CS: Game Development 1",
|
||||
|
@ -61,7 +67,8 @@ var courses =
|
|||
duration: NumberInt(5),
|
||||
free: false,
|
||||
//screenshot: "/images/pages/courses/105_info.png",
|
||||
adminOnly: true
|
||||
adminOnly: true, // Until we finish transitioning to releasePhase
|
||||
releasePhase: 'beta'
|
||||
},
|
||||
{
|
||||
name: "CS: Web Development 1",
|
||||
|
@ -71,7 +78,8 @@ var courses =
|
|||
duration: NumberInt(5),
|
||||
free: false,
|
||||
//screenshot: "/images/pages/courses/105_info.png",
|
||||
adminOnly: true
|
||||
adminOnly: true, // Until we finish transitioning to releasePhase
|
||||
releasePhase: 'beta'
|
||||
},
|
||||
{
|
||||
name: "CS: Web Development 2",
|
||||
|
@ -81,7 +89,8 @@ var courses =
|
|||
duration: NumberInt(5),
|
||||
free: false,
|
||||
//screenshot: "/images/pages/courses/105_info.png",
|
||||
adminOnly: true
|
||||
adminOnly: true, // Until we finish transitioning to releasePhase
|
||||
releasePhase: 'beta'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -167,7 +167,7 @@ module.exports =
|
|||
generateCoursesData: co.wrap (req) ->
|
||||
# helper function for generating the latest version of courses
|
||||
query = {}
|
||||
query = {adminOnly: {$ne: true}} unless req.user?.isAdmin()
|
||||
query = {releasePhase: 'released'} unless req.user?.isAdmin()
|
||||
courses = yield Course.find(query)
|
||||
courses = Course.sortCourses courses
|
||||
campaigns = yield Campaign.find({_id: {$in: (course.get('campaignID') for course in courses)}})
|
||||
|
|
|
@ -51,9 +51,9 @@ module.exports =
|
|||
res.status(200).send(level)
|
||||
|
||||
get: (Model, options={}) -> wrap (req, res) ->
|
||||
# Don't use standard rest middleware get, because we want to filter out adminOnly courses for non-admins
|
||||
query = {}
|
||||
query = {adminOnly: {$ne: true}} unless req.user?.isAdmin()
|
||||
if req.query.releasePhase
|
||||
query.releasePhase = req.query.releasePhase
|
||||
dbq = Model.find(query)
|
||||
dbq.select(parse.getProjectFromReq(req))
|
||||
results = yield database.viewSearch(dbq, req)
|
||||
|
|
|
@ -90,6 +90,7 @@ describe 'POST /db/classroom', ->
|
|||
[res, body] = yield request.postAsync({uri: getURL('/db/level'), json: levelJSONC})
|
||||
expect(res.statusCode).toBe(200)
|
||||
@levelC = yield Level.findById(res.body._id)
|
||||
|
||||
campaignJSON = { name: 'Campaign', levels: {} }
|
||||
paredLevelC = _.pick(@levelC.toObject(), 'name', 'original', 'type', 'slug', 'practice')
|
||||
paredLevelC.campaignIndex = 2
|
||||
|
@ -100,9 +101,10 @@ describe 'POST /db/classroom', ->
|
|||
paredLevelA = _.pick(@levelA.toObject(), 'name', 'original', 'type', 'slug')
|
||||
paredLevelA.campaignIndex = 0
|
||||
campaignJSON.levels[@levelA.get('original').toString()] = paredLevelA
|
||||
|
||||
[res, body] = yield request.postAsync({uri: getURL('/db/campaign'), json: campaignJSON})
|
||||
@campaign = yield Campaign.findById(res.body._id)
|
||||
@course = Course({name: 'Course', campaignID: @campaign._id})
|
||||
@course = Course({name: 'Course', campaignID: @campaign._id, releasePhase: 'released'})
|
||||
yield @course.save()
|
||||
done()
|
||||
|
||||
|
@ -144,6 +146,57 @@ describe 'POST /db/classroom', ->
|
|||
expect(classroom.get('courses')[0].levels[0].name).toBe('Level A')
|
||||
done()
|
||||
|
||||
describe 'when there are unreleased courses', ->
|
||||
beforeEach utils.wrap (done) ->
|
||||
admin = yield utils.initAdmin()
|
||||
yield utils.loginUser(admin)
|
||||
|
||||
betaLevelJSON = { name: 'Beta Level', permissions: [{access: 'owner', target: admin.id}], type: 'course' }
|
||||
[res, body] = yield request.postAsync({uri: getURL('/db/level'), json: betaLevelJSON})
|
||||
expect(res.statusCode).toBe(200)
|
||||
@betaLevel = yield Level.findById(res.body._id)
|
||||
|
||||
betaCampaignJSON = { name: 'Beta Campaign', levels: {} }
|
||||
paredBetaLevel = _.pick(@betaLevel.toObject(), 'name', 'original', 'type', 'slug')
|
||||
paredBetaLevel.campaignIndex = 0
|
||||
betaCampaignJSON.levels[@betaLevel.get('original').toString()] = paredBetaLevel
|
||||
|
||||
[res, body] = yield request.postAsync({uri: getURL('/db/campaign'), json: betaCampaignJSON})
|
||||
@betaCampaign = yield Campaign.findById(res.body._id)
|
||||
@betaCourse = Course({name: 'Beta Course', campaignID: @betaCampaign._id, releasePhase: 'beta'})
|
||||
yield @betaCourse.save()
|
||||
done()
|
||||
|
||||
it 'includes unreleased courses for admin teachers', utils.wrap (done) ->
|
||||
adminTeacher = yield utils.initUser({ role: 'teacher', permissions: ['admin'] })
|
||||
yield utils.loginUser(adminTeacher)
|
||||
data = { name: 'Classroom 3' }
|
||||
[res, body] = yield request.postAsync {uri: classroomsURL, json: data }
|
||||
classroom = yield Classroom.findById(res.body._id)
|
||||
expect(classroom.get('courses').length).toBe(2)
|
||||
expect(classroom.get('courses')[0].levels[0].original.toString()).toBe(@levelA.get('original').toString())
|
||||
expect(classroom.get('courses')[0].levels[0].type).toBe('course')
|
||||
expect(classroom.get('courses')[0].levels[0].slug).toBe('level-a')
|
||||
expect(classroom.get('courses')[0].levels[0].name).toBe('Level A')
|
||||
expect(classroom.get('courses')[1].levels[0].original.toString()).toBe(@betaLevel.get('original').toString())
|
||||
expect(classroom.get('courses')[1].levels[0].type).toBe('course')
|
||||
expect(classroom.get('courses')[1].levels[0].slug).toBe('beta-level')
|
||||
expect(classroom.get('courses')[1].levels[0].name).toBe('Beta Level')
|
||||
done()
|
||||
|
||||
it 'does not include unreleased courses for non-admin teachers', utils.wrap (done) ->
|
||||
teacher = yield utils.initUser({role: 'teacher'})
|
||||
yield utils.loginUser(teacher)
|
||||
data = { name: 'Classroom 4' }
|
||||
[res, body] = yield request.postAsync {uri: classroomsURL, json: data }
|
||||
classroom = yield Classroom.findById(res.body._id)
|
||||
expect(classroom.get('courses').length).toBe(1)
|
||||
expect(classroom.get('courses')[0].levels[0].original.toString()).toBe(@levelA.get('original').toString())
|
||||
expect(classroom.get('courses')[0].levels[0].type).toBe('course')
|
||||
expect(classroom.get('courses')[0].levels[0].slug).toBe('level-a')
|
||||
expect(classroom.get('courses')[0].levels[0].name).toBe('Level A')
|
||||
done()
|
||||
|
||||
describe 'GET /db/classroom/:handle/levels', ->
|
||||
|
||||
beforeEach utils.wrap (done) ->
|
||||
|
@ -159,7 +212,7 @@ describe 'GET /db/classroom/:handle/levels', ->
|
|||
campaignJSON.levels[res.body.original] = paredLevel
|
||||
[res, body] = yield request.postAsync({uri: getURL('/db/campaign'), json: campaignJSON})
|
||||
@campaign = yield Campaign.findById(res.body._id)
|
||||
@course = Course({name: 'Course', campaignID: @campaign._id})
|
||||
@course = Course({name: 'Course', campaignID: @campaign._id, releasePhase: 'released'})
|
||||
yield @course.save()
|
||||
teacher = yield utils.initUser({role: 'teacher'})
|
||||
yield utils.loginUser(teacher)
|
||||
|
@ -206,10 +259,10 @@ describe 'GET /db/classroom/:handle/levels', ->
|
|||
[res, body] = yield request.postAsync({uri: getURL('/db/campaign'), json: campaignJSONB})
|
||||
@campaignB = yield Campaign.findById(res.body._id)
|
||||
|
||||
@courseA = Course({name: 'Course A', campaignID: @campaignA._id})
|
||||
@courseA = Course({name: 'Course A', campaignID: @campaignA._id, releasePhase: 'released'})
|
||||
yield @courseA.save()
|
||||
|
||||
@courseB = Course({name: 'Course B', campaignID: @campaignB._id})
|
||||
@courseB = Course({name: 'Course B', campaignID: @campaignB._id, releasePhase: 'released'})
|
||||
yield @courseB.save()
|
||||
|
||||
teacher = yield utils.initUser({role: 'teacher'})
|
||||
|
@ -286,7 +339,7 @@ describe 'POST /db/classroom/-/members', ->
|
|||
yield utils.clearModels([User, Classroom, Course, Campaign])
|
||||
@campaign = new Campaign({levels: {}})
|
||||
yield @campaign.save()
|
||||
@course = new Course({free: true, campaignID: @campaign._id})
|
||||
@course = new Course({free: true, campaignID: @campaign._id, releasePhase: 'released'})
|
||||
yield @course.save()
|
||||
@teacher = yield utils.initUser({role: 'teacher'})
|
||||
yield utils.loginUser(@teacher)
|
||||
|
@ -560,7 +613,7 @@ describe 'GET /db/classroom/:handle/update-courses', ->
|
|||
teacher = yield utils.initUser({role: 'teacher'})
|
||||
|
||||
yield utils.loginUser(admin)
|
||||
yield utils.makeCourse({}, {campaign: yield utils.makeCampaign()})
|
||||
yield utils.makeCourse({releasePhase: 'released'}, {campaign: yield utils.makeCampaign()})
|
||||
|
||||
yield utils.loginUser(teacher)
|
||||
data = { name: 'Classroom 2' }
|
||||
|
@ -569,7 +622,7 @@ describe 'GET /db/classroom/:handle/update-courses', ->
|
|||
expect(classroom.get('courses').length).toBe(1)
|
||||
|
||||
yield utils.loginUser(admin)
|
||||
yield utils.makeCourse({}, {campaign: yield utils.makeCampaign()})
|
||||
yield utils.makeCourse({releasePhase: 'released'}, {campaign: yield utils.makeCampaign()})
|
||||
|
||||
classroom = yield Classroom.findById(res.body._id)
|
||||
expect(classroom.get('courses').length).toBe(1)
|
||||
|
@ -580,5 +633,4 @@ describe 'GET /db/classroom/:handle/update-courses', ->
|
|||
|
||||
classroom = yield Classroom.findById(res.body._id)
|
||||
expect(classroom.get('courses').length).toBe(2)
|
||||
done()
|
||||
|
||||
done()
|
||||
|
|
|
@ -109,7 +109,7 @@ describe 'POST /db/course_instance/:id/members', ->
|
|||
yield utils.loginUser(admin)
|
||||
@level = yield utils.makeLevel({type: 'course'})
|
||||
@campaign = yield utils.makeCampaign({}, {levels: [@level]})
|
||||
@course = yield utils.makeCourse({free: true}, {campaign: @campaign})
|
||||
@course = yield utils.makeCourse({free: true, releasePhase: 'released'}, {campaign: @campaign})
|
||||
@student = yield utils.initUser({role: 'student'})
|
||||
@prepaid = yield utils.makePrepaid({creator: @teacher.id})
|
||||
members = [@student]
|
||||
|
@ -291,10 +291,10 @@ describe 'GET /db/course_instance/:handle/levels/:levelOriginal/next', ->
|
|||
[res, body] = yield request.postAsync({uri: getURL('/db/campaign'), json: campaignJSONB})
|
||||
@campaignB = yield Campaign.findById(res.body._id)
|
||||
|
||||
@courseA = Course({name: 'Course A', campaignID: @campaignA._id})
|
||||
@courseA = Course({name: 'Course A', campaignID: @campaignA._id, releasePhase: 'released'})
|
||||
yield @courseA.save()
|
||||
|
||||
@courseB = Course({name: 'Course B', campaignID: @campaignB._id})
|
||||
@courseB = Course({name: 'Course B', campaignID: @campaignB._id, releasePhase: 'released'})
|
||||
yield @courseB.save()
|
||||
|
||||
yield utils.loginUser(teacher)
|
||||
|
@ -379,7 +379,7 @@ describe 'GET /db/course_instance/:handle/course', ->
|
|||
|
||||
beforeEach utils.wrap (done) ->
|
||||
yield utils.clearModels [User, CourseInstance, Classroom]
|
||||
@course = new Course({})
|
||||
@course = new Course({ releasePhase: 'released' })
|
||||
yield @course.save()
|
||||
@courseInstance = new CourseInstance({courseID: @course._id})
|
||||
yield @courseInstance.save()
|
||||
|
@ -404,7 +404,7 @@ describe 'POST /db/course_instance/-/recent', ->
|
|||
@admin = yield utils.initAdmin()
|
||||
yield utils.loginUser(@admin)
|
||||
@campaign = yield utils.makeCampaign()
|
||||
@course = yield utils.makeCourse({free: true}, {campaign: @campaign})
|
||||
@course = yield utils.makeCourse({free: true, releasePhase: 'released'}, {campaign: @campaign})
|
||||
@student = yield utils.initUser({role: 'student'})
|
||||
@prepaid = yield utils.makePrepaid({creator: @teacher.id})
|
||||
members = [@student]
|
||||
|
|
|
@ -21,8 +21,8 @@ courseFixture = {
|
|||
describe 'GET /db/course', ->
|
||||
beforeEach utils.wrap (done) ->
|
||||
yield utils.clearModels([Course, User])
|
||||
yield new Course({ name: 'Course 1' }).save()
|
||||
yield new Course({ name: 'Course 2' }).save()
|
||||
yield new Course({ name: 'Course 1', releasePhase: 'released' }).save()
|
||||
yield new Course({ name: 'Course 2', releasePhase: 'released' }).save()
|
||||
yield utils.becomeAnonymous()
|
||||
done()
|
||||
|
||||
|
@ -36,7 +36,7 @@ describe 'GET /db/course/:handle', ->
|
|||
|
||||
beforeEach utils.wrap (done) ->
|
||||
yield utils.clearModels([Course, User])
|
||||
@course = yield new Course({ name: 'Some Name' }).save()
|
||||
@course = yield new Course({ name: 'Some Name', releasePhase: 'released' }).save()
|
||||
yield utils.becomeAnonymous()
|
||||
done()
|
||||
|
||||
|
@ -96,10 +96,10 @@ describe 'GET /db/course/:handle/levels/:levelOriginal/next', ->
|
|||
[res, body] = yield request.postAsync({uri: getURL('/db/campaign'), json: campaignJSONB})
|
||||
@campaignB = yield Campaign.findById(res.body._id)
|
||||
|
||||
@courseA = Course({name: 'Course A', campaignID: @campaignA._id})
|
||||
@courseA = Course({name: 'Course A', campaignID: @campaignA._id, releasePhase: 'released'})
|
||||
yield @courseA.save()
|
||||
|
||||
@courseB = Course({name: 'Course B', campaignID: @campaignB._id})
|
||||
@courseB = Course({name: 'Course B', campaignID: @campaignB._id, releasePhase: 'released'})
|
||||
yield @courseB.save()
|
||||
|
||||
teacher = yield utils.initUser({role: 'teacher'})
|
||||
|
|
|
@ -67,11 +67,11 @@ describe 'GET /db/level/:handle/session', ->
|
|||
@level = yield utils.makeLevel({type: 'course'})
|
||||
|
||||
# To ensure test compares original, not id, make them different. TODO: Make factories do this normally?
|
||||
@level.set('original', new mongoose.Types.ObjectId())
|
||||
@level.set('original', new mongoose.Types.ObjectId())
|
||||
@level.save()
|
||||
|
||||
@campaign = yield utils.makeCampaign({}, {levels: [@level]})
|
||||
@course = yield utils.makeCourse({free: true}, {campaign: @campaign})
|
||||
@course = yield utils.makeCourse({free: true, releasePhase: 'released'}, {campaign: @campaign})
|
||||
@student = yield utils.initUser({role: 'student'})
|
||||
members = [@student]
|
||||
teacher = yield utils.initUser({role: 'teacher'})
|
||||
|
@ -125,7 +125,7 @@ describe 'GET /db/level/:handle/session', ->
|
|||
|
||||
it 'creates the session if the user is enrolled', utils.wrap (done) ->
|
||||
@student.set({
|
||||
coursePrepaid: {
|
||||
coursePrepaid: {
|
||||
_id: {}
|
||||
startDate: moment().subtract(1, 'month').toISOString()
|
||||
endDate: moment().add(1, 'month').toISOString()
|
||||
|
|
|
@ -140,6 +140,8 @@ module.exports = mw =
|
|||
if sources.campaign and not data.campaignID
|
||||
data.campaignID = sources.campaign._id
|
||||
|
||||
data.releasePhase ||= 'released'
|
||||
|
||||
course = new Course(data)
|
||||
return course.save()
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ module.exports = {
|
|||
attrs = _.extend({}, {
|
||||
_id: _id
|
||||
name: _.string.humanize(_id)
|
||||
releasePhase: 'released'
|
||||
}, attrs)
|
||||
|
||||
attrs.campaignID ?= sources.campaign?.id or _.uniqueId('campaign_')
|
||||
|
@ -185,6 +186,5 @@ module.exports = {
|
|||
organization: 'Greendale'
|
||||
}
|
||||
}, attrs)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue