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
efeabe7a34
15 changed files with 232 additions and 34 deletions
|
@ -169,3 +169,8 @@ module.exports = class Classroom extends CocoModel
|
|||
options.url = @url() + '/invite-members'
|
||||
options.type = 'POST'
|
||||
@fetch(options)
|
||||
|
||||
updateCourses: (options={}) ->
|
||||
options.url = @url() + '/update-courses'
|
||||
options.type = 'POST'
|
||||
@fetch(options)
|
||||
|
|
|
@ -65,3 +65,9 @@ block modal-footer-content
|
|||
button#save-settings-btn.btn.btn-primary.btn-lg(data-i18n="courses.create_class")
|
||||
else
|
||||
button#save-settings-btn.btn.btn-primary.btn-lg(data-i18n="common.save_changes")
|
||||
|
||||
if me.isAdmin()
|
||||
hr
|
||||
// DNT
|
||||
h3 Admin Only
|
||||
button#update-courses-btn.btn.btn-forest.btn-lg Update Courses
|
||||
|
|
|
@ -122,10 +122,10 @@ block content
|
|||
li(class=(activeTab === "#enrollment-status-tab" ? 'active' : ''))
|
||||
a.course-progress-tab-btn(href='#enrollment-status-tab')
|
||||
.small-details.text-center(data-i18n='teacher.enrollment_status')
|
||||
.tab-spacer
|
||||
li(class=(activeTab === "#student-projects-tab" ? 'active' : ''))
|
||||
a.course-progress-tab-btn(href='#student-projects-tab')
|
||||
.small-details.text-center(data-i18n='teacher.projects')
|
||||
//.tab-spacer
|
||||
//li(class=(activeTab === "#student-projects-tab" ? 'active' : ''))
|
||||
// a.course-progress-tab-btn(href='#student-projects-tab')
|
||||
// .small-details.text-center(data-i18n='teacher.projects')
|
||||
.tab-filler
|
||||
|
||||
.tab-content
|
||||
|
|
|
@ -10,6 +10,7 @@ module.exports = class ClassroomSettingsModal extends ModalView
|
|||
|
||||
events:
|
||||
'click #save-settings-btn': 'onSubmitForm'
|
||||
'click #update-courses-btn': 'onClickUpdateCoursesButton'
|
||||
'submit form': 'onSubmitForm'
|
||||
|
||||
initialize: (options={}) ->
|
||||
|
@ -51,3 +52,13 @@ module.exports = class ClassroomSettingsModal extends ModalView
|
|||
@listenToOnce @classroom, 'sync', @hide
|
||||
window.tracker?.trackEvent "Teachers Edit Class Saved", category: 'Teachers', classroomID: @classroom.id, ['Mixpanel']
|
||||
|
||||
onClickUpdateCoursesButton: ->
|
||||
@$('#update-courses-btn').attr('disabled', true)
|
||||
Promise.resolve(@classroom.updateCourses())
|
||||
.then =>
|
||||
@$('#update-courses-btn').attr('disabled', false)
|
||||
noty { text: 'Updated', timeout: 2000 }
|
||||
.catch (e) =>
|
||||
console.log 'e', e
|
||||
@$('#update-courses-btn').attr('disabled', false)
|
||||
noty { text: e.responseJSON?.message or e.responseText or 'Error!', type: 'error', timeout: 5000 }
|
||||
|
|
|
@ -489,7 +489,7 @@ module.exports = class PlayLevelView extends RootView
|
|||
return false if heapLimit < defaultHeapLimit
|
||||
return false if @loadDuration > 18000
|
||||
else
|
||||
console.warn "Unwritten level type simulation heuristics; fill these in for new level type #{levelType}?"
|
||||
console.warn "Unwritten level type simulation heuristics; fill these in for new level type #{@level.get('type')}?"
|
||||
return false if stillBuggy
|
||||
return false if cores < 8
|
||||
return false if heapLimit < defaultHeapLimit
|
||||
|
|
|
@ -51,10 +51,8 @@ module.exports = class WebSurfaceView extends CocoView
|
|||
if dekuTree.type is '#text'
|
||||
return { virtualDom: dekuTree, styles: [], scripts: [] }
|
||||
if dekuTree.type is 'style'
|
||||
console.log 'Found a style: ', dekuTree
|
||||
return { styles: [dekuTree], scripts: [] }
|
||||
if dekuTree.type is 'script'
|
||||
console.log 'Found a script: ', dekuTree
|
||||
return { styles: [], scripts: [dekuTree] }
|
||||
# recurse over children
|
||||
childStyles = []
|
||||
|
|
|
@ -32,7 +32,9 @@ module.exports = class GameMenuModal extends ModalView
|
|||
docs = @options.level.get('documentation') ? {}
|
||||
submenus = ['guide', 'options', 'save-load']
|
||||
submenus = _.without submenus, 'options' if window.serverConfig.picoCTF
|
||||
submenus = _.without submenus, 'guide' unless docs.specificArticles?.length or docs.generalArticles?.length or window.serverConfig.picoCTF
|
||||
unless window.serverConfig.picoCTF
|
||||
if @level.isType('course', 'course-ladder') or not @options.level.get('helpVideos')?.length > 0
|
||||
submenus = _.without submenus, 'guide'
|
||||
submenus = _.without submenus, 'save-load' unless me.isAdmin() or /https?:\/\/localhost/.test(window.location.href)
|
||||
@includedSubmenus = submenus
|
||||
context.showTab = @options.showTab ? submenus[0]
|
||||
|
|
|
@ -28,23 +28,26 @@ module.exports = class LevelGuideView extends CocoView
|
|||
@videoLocked = not (@helpVideo?.free or @isCourseLevel) and @requiresSubscription
|
||||
|
||||
@firstOnly = options.firstOnly
|
||||
@docs = options?.docs ? options.level.get('documentation') ? {}
|
||||
general = @docs.generalArticles or []
|
||||
specific = @docs.specificArticles or []
|
||||
if window.serverConfig.picoCTF
|
||||
@docs = options?.docs ? options.level.get('documentation') ? {}
|
||||
general = @docs.generalArticles or []
|
||||
specific = @docs.specificArticles or []
|
||||
|
||||
articles = options.supermodel.getModels(Article)
|
||||
articleMap = {}
|
||||
articleMap[article.get('original')] = article for article in articles
|
||||
general = (articleMap[ref.original] for ref in general)
|
||||
general = (article.attributes for article in general when article)
|
||||
articles = options.supermodel.getModels(Article)
|
||||
articleMap = {}
|
||||
articleMap[article.get('original')] = article for article in articles
|
||||
general = (articleMap[ref.original] for ref in general)
|
||||
general = (article.attributes for article in general when article)
|
||||
|
||||
@docs = specific.concat(general)
|
||||
@docs = $.extend(true, [], @docs)
|
||||
@docs = [@docs[0]] if @firstOnly and @docs[0]
|
||||
@addPicoCTFProblem() if window.serverConfig.picoCTF
|
||||
doc.html = marked(utils.filterMarkdownCodeLanguages(utils.i18n(doc, 'body'), options.session.get('codeLanguage'))) for doc in @docs
|
||||
doc.slug = _.string.slugify(doc.name) for doc in @docs
|
||||
doc.name = (utils.i18n doc, 'name') for doc in @docs
|
||||
@docs = specific.concat(general)
|
||||
@docs = $.extend(true, [], @docs)
|
||||
@docs = [@docs[0]] if @firstOnly and @docs[0]
|
||||
@addPicoCTFProblem()
|
||||
doc.html = marked(utils.filterMarkdownCodeLanguages(utils.i18n(doc, 'body'), options.session.get('codeLanguage'))) for doc in @docs
|
||||
doc.slug = _.string.slugify(doc.name) for doc in @docs
|
||||
doc.name = (utils.i18n doc, 'name') for doc in @docs
|
||||
else
|
||||
@docs = []
|
||||
|
||||
destroy: ->
|
||||
if @vimeoListenerAttached
|
||||
|
|
|
@ -24,7 +24,6 @@ getZPContacts((err, emailContactMap) => {
|
|||
const tasks = [];
|
||||
for (const email in emailContactMap) {
|
||||
const contact = emailContactMap[email];
|
||||
// if (contact.organization !== 'Cabarrus County Schools') continue;
|
||||
tasks.push(createUpsertCloseLeadFn(contact));
|
||||
}
|
||||
async.parallel(tasks, (err, results) => {
|
||||
|
@ -147,8 +146,10 @@ function getZPContactsPage(contacts, searchQuery, done) {
|
|||
if (err) return done(err);
|
||||
const data = JSON.parse(body);
|
||||
for (let contact of data.contacts) {
|
||||
let organization = contact.organization_name;
|
||||
if (contact.custom_fields && contact.custom_fields.school_name) organization = contact.custom_fields.school_name;
|
||||
contacts.push({
|
||||
organization: contact.organization_name,
|
||||
organization: organization,
|
||||
name: contact.name,
|
||||
title: contact.title,
|
||||
email: contact.email,
|
||||
|
|
32
scripts/mongodb/createLicenses.js
Normal file
32
scripts/mongodb/createLicenses.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
// Create 100 long-lasting licenses for a given user
|
||||
|
||||
// Usage
|
||||
// ---------------
|
||||
// In mongo shell
|
||||
//
|
||||
// > createLicenses('<user id string>');
|
||||
|
||||
var createLicenses = function updatePrepaid(userStringID) {
|
||||
try {
|
||||
var userID = ObjectId(userStringID);
|
||||
}
|
||||
catch (e) {
|
||||
print('Invalid ObjectId string given:', userStringID, e);
|
||||
return;
|
||||
}
|
||||
|
||||
var user = db.users.findOne({_id: userID});
|
||||
if (!user) {
|
||||
print('User not found');
|
||||
return;
|
||||
}
|
||||
|
||||
db.prepaids.save({
|
||||
redeemers: [],
|
||||
maxRedeemers: 100,
|
||||
startDate: "2000-01-01T00:00:00.000Z",
|
||||
endDate: "3000-01-01T00:00:00.000Z",
|
||||
type: 'course',
|
||||
creator: userID
|
||||
})
|
||||
};
|
88
scripts/mongodb/stored/updatePrepaid.js
Normal file
88
scripts/mongodb/stored/updatePrepaid.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
|
||||
// Update a prepaid document, and the denormalized data on user documents
|
||||
// Limits the properties allowed to be set, but does not perform validation on them. Use carefully!
|
||||
|
||||
// Usage
|
||||
// ---------------
|
||||
// In mongo shell
|
||||
//
|
||||
// > db.loadServerScripts();
|
||||
// > updatePrepaid('<prepaid id string>', { endDate: "2017-07-01T00:00:00.000Z", maxRedeemers: 10, creator: '56141d0a7291607006ad8412' });
|
||||
|
||||
var updatePrepaid = function updatePrepaid(stringID, originalUpdate) {
|
||||
try {
|
||||
load('./bower_components/lodash/dist/lodash.js')
|
||||
}
|
||||
catch (e) {
|
||||
print('Lodash could not be loaded, ensure you are in codecombat project directory.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var id = ObjectId(stringID);
|
||||
}
|
||||
catch (e) {
|
||||
print('Invalid ObjectId given:', stringID);
|
||||
return;
|
||||
}
|
||||
|
||||
var prepaid = db.prepaids.findOne({_id: id});
|
||||
if (!prepaid) {
|
||||
print('Prepaid not found');
|
||||
return;
|
||||
}
|
||||
|
||||
print('Found prepaid', JSON.stringify(_.omit(prepaid, 'redeemers'), null, ' '));
|
||||
print('-- has', _.size(prepaid.redeemers), 'redeemers.');
|
||||
|
||||
var prepaidUpdate = _.pick(originalUpdate, 'maxRedeemers', 'startDate', 'endDate', 'creator');
|
||||
|
||||
if (_.isString(prepaidUpdate.creator)) {
|
||||
try {
|
||||
prepaidUpdate.creator = ObjectId(prepaidUpdate.creator);
|
||||
}
|
||||
catch (e) {
|
||||
print('Invalid creator given:', stringID);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_.isEmpty(prepaidUpdate)) {
|
||||
print('\nSkipping prepaid update, nothing to update.')
|
||||
}
|
||||
else {
|
||||
print('\nUpdate prepaid',
|
||||
JSON.stringify(prepaidUpdate, null, ' '),
|
||||
db.prepaids.update(
|
||||
{ _id: id },
|
||||
{ $set: prepaidUpdate }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
var userUpdate = _(originalUpdate)
|
||||
.pick('startDate', 'endDate' )
|
||||
.transform(function(result, value, key) { result['coursePrepaid.'+key] = value })
|
||||
.value();
|
||||
|
||||
if (_.isEmpty(userUpdate)) {
|
||||
print('\nSkipping user update, nothing to update.')
|
||||
}
|
||||
else {
|
||||
print('\nUpdate users',
|
||||
JSON.stringify(userUpdate, null, ' '),
|
||||
db.users.update(
|
||||
{'coursePrepaid._id': id},
|
||||
{ $set: userUpdate },
|
||||
{ multi: true }
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
db.system.js.save(
|
||||
{
|
||||
_id: 'updatePrepaid',
|
||||
value: updatePrepaid
|
||||
}
|
||||
);
|
|
@ -60,11 +60,11 @@ const closeIoApiKey = process.argv[2];
|
|||
const closeIoMailApiKeys = [
|
||||
{
|
||||
apiKey: process.argv[3],
|
||||
weight: .7
|
||||
weight: .8
|
||||
},
|
||||
{
|
||||
apiKey: process.argv[4],
|
||||
weight: .20
|
||||
weight: .1
|
||||
},
|
||||
{
|
||||
apiKey: process.argv[5],
|
||||
|
@ -327,7 +327,7 @@ function findCocoLeads(done) {
|
|||
if (!trialRequest.properties || !trialRequest.properties.email) continue;
|
||||
const email = trialRequest.properties.email.toLowerCase();
|
||||
emails.push(email);
|
||||
const name = trialRequest.properties.organization || trialRequest.properties.name || email;
|
||||
const name = trialRequest.properties.nces_name || trialRequest.properties.organization || trialRequest.properties.school || email;
|
||||
if (!leads[name]) leads[name] = new CocoLead(name);
|
||||
leads[name].addTrialRequest(email, trialRequest);
|
||||
emailLeadMap[email] = leads[name];
|
||||
|
|
|
@ -17,6 +17,7 @@ User = require '../models/User'
|
|||
CourseInstance = require '../models/CourseInstance'
|
||||
TrialRequest = require '../models/TrialRequest'
|
||||
sendwithus = require '../sendwithus'
|
||||
co = require 'co'
|
||||
|
||||
module.exports =
|
||||
fetchByCode: wrap (req, res, next) ->
|
||||
|
@ -142,6 +143,29 @@ module.exports =
|
|||
database.assignBody(req, classroom)
|
||||
|
||||
# Copy over data from how courses are right now
|
||||
coursesData = yield module.exports.generateCoursesData(req)
|
||||
classroom.set('courses', coursesData)
|
||||
|
||||
# finish
|
||||
database.validateDoc(classroom)
|
||||
classroom = yield classroom.save()
|
||||
res.status(201).send(classroom.toObject({req: req}))
|
||||
|
||||
updateCourses: wrap (req, res) ->
|
||||
throw new errors.Unauthorized() unless req.user and not req.user.isAnonymous()
|
||||
classroom = yield database.getDocFromHandle(req, Classroom)
|
||||
if not classroom
|
||||
throw new errors.NotFound('Classroom not found.')
|
||||
unless req.user._id.equals(classroom.get('ownerID'))
|
||||
throw new errors.Forbidden('Only the owner may update their classroom content')
|
||||
|
||||
coursesData = yield module.exports.generateCoursesData(req)
|
||||
classroom.set('courses', coursesData)
|
||||
classroom = yield classroom.save()
|
||||
res.status(200).send(classroom.toObject({req: req}))
|
||||
|
||||
generateCoursesData: co.wrap (req) ->
|
||||
# helper function for generating the latest version of courses
|
||||
query = {}
|
||||
query = {adminOnly: {$ne: true}} unless req.user?.isAdmin()
|
||||
courses = yield Course.find(query)
|
||||
|
@ -160,12 +184,7 @@ module.exports =
|
|||
_.extend(levelData, _.pick(level, 'type', 'slug', 'name', 'practice', 'practiceThresholdMinutes', 'shareable'))
|
||||
courseData.levels.push(levelData)
|
||||
coursesData.push(courseData)
|
||||
classroom.set('courses', coursesData)
|
||||
|
||||
# finish
|
||||
database.validateDoc(classroom)
|
||||
classroom = yield classroom.save()
|
||||
res.status(201).send(classroom.toObject({req: req}))
|
||||
return coursesData
|
||||
|
||||
join: wrap (req, res) ->
|
||||
unless req.body?.code
|
||||
|
|
|
@ -71,6 +71,7 @@ module.exports.setup = (app) ->
|
|||
app.get('/db/classroom/:handle/members', mw.classrooms.fetchMembers) # TODO: Use mw.auth?
|
||||
app.post('/db/classroom/:classroomID/members/:memberID/reset-password', mw.classrooms.setStudentPassword)
|
||||
app.post('/db/classroom/:anything/members', mw.auth.checkLoggedIn(), mw.classrooms.join)
|
||||
app.post('/db/classroom/:handle/update-courses', mw.classrooms.updateCourses)
|
||||
app.get('/db/classroom/:handle', mw.auth.checkLoggedIn()) # TODO: Finish migrating route, adding now so 401 is returned
|
||||
app.get('/db/classroom/-/users', mw.auth.checkHasPermission(['admin']), mw.classrooms.getUsers)
|
||||
|
||||
|
|
|
@ -550,3 +550,35 @@ describe 'POST /db/classroom/:classroomID/members/:memberID/reset-password', ->
|
|||
changedStudent = yield User.findById(student.id)
|
||||
expect(changedStudent.get('passwordHash')).toEqual(student.get('passwordHash'))
|
||||
done()
|
||||
|
||||
describe 'GET /db/classroom/:handle/update-courses', ->
|
||||
|
||||
it 'updates the courses property for that classroom', utils.wrap (done) ->
|
||||
yield utils.clearModels [User, Classroom, Course, Level, Campaign]
|
||||
|
||||
admin = yield utils.initAdmin()
|
||||
teacher = yield utils.initUser({role: 'teacher'})
|
||||
|
||||
yield utils.loginUser(admin)
|
||||
yield utils.makeCourse({}, {campaign: yield utils.makeCampaign()})
|
||||
|
||||
yield utils.loginUser(teacher)
|
||||
data = { name: 'Classroom 2' }
|
||||
[res, body] = yield request.postAsync {uri: classroomsURL, json: data }
|
||||
classroom = yield Classroom.findById(res.body._id)
|
||||
expect(classroom.get('courses').length).toBe(1)
|
||||
|
||||
yield utils.loginUser(admin)
|
||||
yield utils.makeCourse({}, {campaign: yield utils.makeCampaign()})
|
||||
|
||||
classroom = yield Classroom.findById(res.body._id)
|
||||
expect(classroom.get('courses').length).toBe(1)
|
||||
|
||||
yield utils.loginUser(teacher)
|
||||
[res, body] = yield request.postAsync { uri: classroomsURL + "/#{classroom.id}/update-courses", json: true }
|
||||
expect(body.courses.length).toBe(2)
|
||||
|
||||
classroom = yield Classroom.findById(res.body._id)
|
||||
expect(classroom.get('courses').length).toBe(2)
|
||||
done()
|
||||
|
||||
|
|
Loading…
Reference in a new issue