codecombat/app/schemas/models/user.coffee

360 lines
18 KiB
CoffeeScript
Raw Normal View History

c = require './../schemas'
2014-04-12 04:35:56 -04:00
emailSubscriptions = ['announcement', 'tester', 'level_creator', 'developer', 'article_editor', 'translator', 'support', 'notification']
2014-07-09 14:23:05 -04:00
UserSchema = c.object
title: 'User'
default:
visa: 'Authorized to work in the US'
music: true
name: 'Anonymous'
autocastDelay: 5000
emails: {}
permissions: []
anonymous: true
points: 0
preferredLanguage: 'en-US'
aceConfig: {}
simulatedBy: 0
simulatedFor: 0
jobProfile: {}
earned: {heroes: [], items: [], levels: [], gems: 0}
purchased: {heroes: [], items: [], levels: [], gems: 0}
2014-07-09 14:23:05 -04:00
c.extendNamedProperties UserSchema # let's have the name be the first property
#Put the various filters in variables for reusability
phoneScreenFilter =
title: 'Phone screened'
type: 'boolean'
description: 'Whether the candidate has been phone screened.'
schoolFilter =
title: 'School'
type: 'string'
enum: ['Top School', 'Other']
locationFilter =
title: 'Location'
type: 'string'
enum: ['Bay Area', 'New York', 'Other US', 'International']
roleFilter =
title: 'Role'
type: 'string'
enum: ['Web Developer', 'Software Developer', 'Mobile Developer']
seniorityFilter =
title: 'Seniority'
type: 'string'
enum: ['College Student', 'Recent Grad', 'Junior', 'Senior']
visa = c.shortString
title: 'US Work Status'
description: 'Are you authorized to work in the US, or do you need visa sponsorship? (If you live in Canada or Australia, mark authorized.)'
enum: ['Authorized to work in the US', 'Need visa sponsorship']
2014-07-09 14:23:05 -04:00
_.extend UserSchema.properties,
2014-04-12 04:35:56 -04:00
email: c.shortString({title: 'Email', format: 'email'})
Improve student account recovery This adds the ability to verify email addresses of a user, so we know they have access to the email address on their account. Until a user has verified their email address, any teacher of a class they're in can reset their password for them via the Teacher Dashboard. When a user's email address is verified, a teacher may trigger a password recovery email to be sent to the student. Verification links are valid forever, until the user changes the email address they have on file. They are created using a timestamp, with a sha256 of timestamp+salt+userID+email. Currently the hash value is rather long, could be shorter. Squashed commit messages: Add server endpoints for verifying email address Add server endpoints for verifying email address (pt 2) Add Server+Client endpoint for sending verification email Add client view for verification links Add Edit Student Modal for resetting passwords Add specs for EditStudentModal Tweak method name in EditStudentModal Add edit student button to TeacherClassView Fix up frontend for teacher password resetting Add middleware for teacher password resetting Improve button UX in EditStudentModal Add JoinClassModal Add welcome emails, use broad name Use email without domain as fallback instead of full email Fetch user on edit student modal open Don't allow password reset if student email is verified Set role to student on user signup with classCode Tweak interface for joinClassModal Add button to request verification email for yourself Fix verify email template ID Move text to en.coffee Minor tweaks Fix code review comments Fix some tests, disable a broken one Fix misc tests Fix more tests Refactor recovery email sending to auth Fix overbroad sass Add options to refactored recovery email function Rename getByCode to fetchByCode Fix error message Fix up error handling in users middleware Use .get instead of .toObject Use findById Fix more code review comments Disable still-broken test
2016-05-11 17:39:26 -04:00
emailVerified: { type: 'boolean' }
2014-11-20 20:03:24 -05:00
iosIdentifierForVendor: c.shortString({format: 'hidden'})
2014-04-12 04:35:56 -04:00
firstName: c.shortString({title: 'First Name'})
lastName: c.shortString({title: 'Last Name'})
gender: {type: 'string'} # , 'enum': ['male', 'female', 'secret', 'trans', 'other']
# NOTE: ageRange enum changed on 4/27/16 from ['0-13', '14-17', '18-24', '25-34', '35-44', '45-100']
ageRange: {type: 'string'} # 'enum': ['13-15', '16-17', '18-24', '25-34', '35-44', '45-100']
password: c.passwordString
2014-04-12 04:35:56 -04:00
passwordReset: {type: 'string'}
photoURL: {type: 'string', format: 'image-file', title: 'Profile Picture', description: 'Upload a 256x256px or larger image to serve as your profile picture.'}
facebookID: c.shortString({title: 'Facebook ID'})
githubID: {type: 'integer', title: 'GitHub ID'}
2014-04-12 04:35:56 -04:00
gplusID: c.shortString({title: 'G+ ID'})
2016-09-13 18:18:19 -04:00
cleverID: c.shortString({title: 'Clever ID'})
2014-04-12 04:35:56 -04:00
wizardColor1: c.pct({title: 'Wizard Clothes Color'}) # No longer used
2014-04-12 04:35:56 -04:00
volume: c.pct({title: 'Volume'})
music: { type: 'boolean' }
autocastDelay: { type: 'integer' } # No longer used
lastLevel: { type: 'string' }
heroConfig: c.HeroConfigSchema
2014-04-12 04:35:56 -04:00
emailSubscriptions: c.array {uniqueItems: true}, {'enum': emailSubscriptions}
emails: c.object {title: 'Email Settings', default: generalNews: {enabled: true}, anyNotes: {enabled: true}, recruitNotes: {enabled: true} },
# newsletters
2014-06-30 22:16:26 -04:00
generalNews: {$ref: '#/definitions/emailSubscription'}
adventurerNews: {$ref: '#/definitions/emailSubscription'}
ambassadorNews: {$ref: '#/definitions/emailSubscription'}
archmageNews: {$ref: '#/definitions/emailSubscription'}
artisanNews: {$ref: '#/definitions/emailSubscription'}
diplomatNews: {$ref: '#/definitions/emailSubscription'}
teacherNews: {$ref: '#/definitions/emailSubscription'}
2014-06-30 22:16:26 -04:00
scribeNews: {$ref: '#/definitions/emailSubscription'}
2014-04-25 13:46:43 -04:00
# notifications
2014-06-30 22:16:26 -04:00
anyNotes: {$ref: '#/definitions/emailSubscription'} # overrides any other notifications settings
recruitNotes: {$ref: '#/definitions/emailSubscription'}
employerNotes: {$ref: '#/definitions/emailSubscription'}
2014-04-12 04:35:56 -04:00
oneTimes: c.array {title: 'One-time emails'},
c.object {title: 'One-time email', required: ['type', 'email']},
type: c.shortString() # E.g 'subscribe modal parent'
email: c.shortString()
sent: c.date() # Set when sent
2014-04-12 04:35:56 -04:00
# server controlled
permissions: c.array {}, c.shortString()
2014-04-12 04:35:56 -04:00
dateCreated: c.date({title: 'Date Joined'})
anonymous: {type: 'boolean' }
2014-04-12 04:35:56 -04:00
testGroupNumber: {type: 'integer', minimum: 0, maximum: 256, exclusiveMaximum: true}
mailChimp: {type: 'object'}
hourOfCode: {type: 'boolean'}
hourOfCodeComplete: {type: 'boolean'}
lastIP: {type: 'string'}
2014-04-12 04:35:56 -04:00
emailLower: c.shortString()
nameLower: c.shortString()
passwordHash: {type: 'string', maxLength: 256}
# client side
emailHash: {type: 'string'}
#Internationalization stuff
preferredLanguage: {'enum': [null].concat(c.getLanguageCodeArray())}
2014-04-25 13:46:43 -04:00
2014-04-12 04:35:56 -04:00
signedCLA: c.date({title: 'Date Signed the CLA'})
wizard: c.object {},
colorConfig: c.object {additionalProperties: c.colorConfig()}
aceConfig: c.object { default: { language: 'python', keyBindings: 'default', invisibles: false, indentGuides: false, behaviors: false, liveCompletion: true }},
2015-12-23 13:38:00 -05:00
language: {type: 'string', 'enum': ['python', 'javascript', 'coffeescript', 'clojure', 'lua', 'java', 'io']}
2016-05-30 20:08:11 -04:00
keyBindings: {type: 'string', 'enum': ['default', 'vim', 'emacs']} # Deprecated 2016-05-30; now we just always give them 'default'.
invisibles: {type: 'boolean' }
indentGuides: {type: 'boolean' }
behaviors: {type: 'boolean' }
liveCompletion: {type: 'boolean' }
2014-04-12 04:35:56 -04:00
simulatedBy: {type: 'integer', minimum: 0 }
simulatedFor: {type: 'integer', minimum: 0 }
2015-12-18 13:34:21 -05:00
# Deprecated. TODO: Figure out how to remove.
jobProfile: c.object {title: 'Job Profile', default: { active: false, lookingFor: 'Full-time', jobTitle: 'Software Developer', city: 'Defaultsville, CA', country: 'USA', skills: ['javascript'], shortDescription: 'Programmer seeking to build great software.', longDescription: '* I write great code.\n* You need great code?\n* Great!' }},
lookingFor: {title: 'Looking For', type: 'string', enum: ['Full-time', 'Part-time', 'Remote', 'Contracting', 'Internship'], description: 'What kind of developer position do you want?'}
jobTitle: {type: 'string', maxLength: 50, title: 'Desired Job Title', description: 'What role are you looking for? Ex.: "Full Stack Engineer", "Front-End Developer", "iOS Developer"' }
active: {title: 'Open to Offers', type: 'boolean', description: 'Want interview offers right now?'}
2014-04-15 12:28:19 -04:00
updated: c.date {title: 'Last Updated', description: 'How fresh your profile appears to employers. Profiles go inactive after 4 weeks.'}
2014-04-12 04:35:56 -04:00
name: c.shortString {title: 'Name', description: 'Name you want employers to see, like "Nick Winter".'}
city: c.shortString {title: 'City', description: 'City you want to work in (or live in now), like "San Francisco" or "Lubbock, TX".', format: 'city'}
country: c.shortString {title: 'Country', description: 'Country you want to work in (or live in now), like "USA" or "France".', format: 'country'}
skills: c.array {title: 'Skills', description: 'Tag relevant developer skills in order of proficiency', maxItems: 30, uniqueItems: true},
{type: 'string', minLength: 1, maxLength: 50, description: 'Ex.: "objective-c", "mongodb", "rails", "android", "javascript"', format: 'skill'}
2014-04-12 04:35:56 -04:00
experience: {type: 'integer', title: 'Years of Experience', minimum: 0, description: 'How many years of professional experience (getting paid) developing software do you have?'}
shortDescription: {type: 'string', maxLength: 140, title: 'Short Description', description: 'Who are you, and what are you looking for? 140 characters max.' }
longDescription: {type: 'string', maxLength: 600, title: 'Description', description: 'Describe yourself to potential employers. Keep it short and to the point. We recommend outlining the position that would most interest you. Tasteful markdown okay; 600 characters max.', format: 'markdown' }
visa: visa
2014-04-12 04:35:56 -04:00
work: c.array {title: 'Work Experience', description: 'List your relevant work experience, most recent first.'},
c.object {title: 'Job', description: 'Some work experience you had.', required: ['employer', 'role', 'duration']},
employer: c.shortString {title: 'Employer', description: 'Name of your employer.'}
role: c.shortString {title: 'Job Title', description: 'What was your job title or role?'}
duration: c.shortString {title: 'Duration', description: 'When did you hold this gig? Ex.: "Feb 2013 - present".'}
description: {type: 'string', title: 'Description', description: 'What did you do there? (140 chars; optional)', maxLength: 140}
2014-04-12 04:35:56 -04:00
education: c.array {title: 'Education', description: 'List your academic ordeals.'},
c.object {title: 'Ordeal', description: 'Some education that befell you.', required: ['school', 'degree', 'duration']},
school: c.shortString {title: 'School', description: 'Name of your school.'}
degree: c.shortString {title: 'Degree', description: 'What was your degree and field of study? Ex. Ph.D. Human-Computer Interaction (incomplete)'}
duration: c.shortString {title: 'Dates', description: 'When? Ex.: "Aug 2004 - May 2008".'}
description: {type: 'string', title: 'Description', description: 'Highlight anything about this educational experience. (140 chars; optional)', maxLength: 140}
projects: c.array {title: 'Projects (Top 3)', description: 'Highlight your projects to amaze employers.', maxItems: 3},
2014-04-12 04:35:56 -04:00
c.object {title: 'Project', description: 'A project you created.', required: ['name', 'description', 'picture'], default: {name: 'My Project', description: 'A project I worked on.', link: 'http://example.com', picture: ''}},
name: c.shortString {title: 'Project Name', description: 'What was the project called?' }
description: {type: 'string', title: 'Description', description: 'Briefly describe the project.', maxLength: 400, format: 'markdown'}
2014-04-12 04:35:56 -04:00
picture: {type: 'string', title: 'Picture', format: 'image-file', description: 'Upload a 230x115px or larger image showing off the project.'}
link: c.url {title: 'Link', description: 'Link to the project.'}
2014-04-12 04:35:56 -04:00
links: c.array {title: 'Personal and Social Links', description: 'Link any other sites or profiles you want to highlight, like your GitHub, your LinkedIn, or your blog.'},
c.object {title: 'Link', description: 'A link to another site you want to highlight, like your GitHub, your LinkedIn, or your blog.', required: ['name', 'link'], default: {link: 'http://example.com'}},
name: {type: 'string', maxLength: 30, title: 'Link Name', description: 'What are you linking to? Ex: "Personal Website", "GitHub"', format: 'link-name'}
link: c.url {title: 'Link', description: 'The URL.' }
2014-04-12 04:35:56 -04:00
photoURL: {type: 'string', format: 'image-file', title: 'Profile Picture', description: 'Upload a 256x256px or larger image if you want to show a different profile picture to employers than your normal avatar.'}
curated: c.object {title: 'Curated', required: ['shortDescription', 'mainTag', 'location', 'education', 'workHistory', 'phoneScreenFilter', 'schoolFilter', 'locationFilter', 'roleFilter', 'seniorityFilter']},
2014-07-03 13:02:42 -04:00
shortDescription:
title: 'Short description'
description: 'A sentence or two describing the candidate'
type: 'string'
mainTag:
title: 'Main tag'
description: 'A main tag to describe this candidate'
type: 'string'
location:
title: 'Location'
description: 'The CURRENT location of the candidate'
type: 'string'
education:
title: 'Education'
description: 'The main educational institution of the candidate'
type: 'string'
workHistory: c.array {title: 'Work history', description: 'One or two places the candidate has worked', type: 'array'},
title: 'Workplace'
type: 'string'
phoneScreenFilter: phoneScreenFilter
schoolFilter: schoolFilter
locationFilter: locationFilter
roleFilter: roleFilter
seniorityFilter: seniorityFilter
2014-07-07 13:42:16 -04:00
featured:
title: 'Featured'
type: 'boolean'
description: 'Should this candidate be prominently featured on the site?'
2014-04-12 04:35:56 -04:00
jobProfileApproved: {title: 'Job Profile Approved', type: 'boolean', description: 'Whether your profile has been approved by CodeCombat.'}
2014-07-16 13:51:44 -04:00
jobProfileApprovedDate: c.date {title: 'Approved date', description: 'The date that the candidate was approved'}
jobProfileNotes: {type: 'string', maxLength: 1000, title: 'Our Notes', description: 'CodeCombat\'s notes on the candidate.', format: 'markdown' }
2014-06-30 22:16:26 -04:00
employerAt: c.shortString {description: 'If given employer permissions to view job candidates, for which employer?'}
2014-04-24 20:36:07 -04:00
signedEmployerAgreement: c.object {},
2014-06-30 22:16:26 -04:00
linkedinID: c.shortString {title: 'LinkedInID', description: 'The user\'s LinkedIn ID when they signed the contract.'}
date: c.date {title: 'Date signed employer agreement'}
data: c.object {description: 'Cached LinkedIn data slurped from profile.', additionalProperties: true}
savedEmployerFilterAlerts: c.array {
title: 'Saved Employer Filter Alerts'
description: 'Employers can get emailed alerts whenever there are new candidates matching their filters'
}, c.object({
title: 'Saved filter set'
description: 'A saved filter set'
2014-07-21 19:10:02 -04:00
required: ['phoneScreenFilter','schoolFilter','locationFilter','roleFilter','seniorityFilter','visa']
}, {
2014-07-21 13:01:49 -04:00
phoneScreenFilter:
title: 'Phone screen filter values'
type: 'array'
items:
2014-07-21 13:01:49 -04:00
type: 'boolean'
schoolFilter:
title: 'School filter values'
type: 'array'
items:
type: schoolFilter.type
enum: schoolFilter.enum
locationFilter:
2014-07-21 13:01:49 -04:00
title: 'Location filter values'
type: 'array'
items:
type: locationFilter.type
enum: locationFilter.enum
roleFilter:
2014-07-21 13:01:49 -04:00
title: 'Role filter values'
type: 'array'
items:
type: roleFilter.type
enum: roleFilter.enum
seniorityFilter:
2014-07-21 13:01:49 -04:00
title: 'Seniority filter values'
type: 'array'
items:
type: roleFilter.type
enum: seniorityFilter.enum
visa:
2014-07-21 13:01:49 -04:00
title: 'Visa filter values'
type: 'array'
items:
type: visa.type
enum: visa.enum
})
2014-06-30 22:16:26 -04:00
points: {type: 'number'}
activity: {type: 'object', description: 'Summary statistics about user activity', additionalProperties: c.activity}
2014-07-23 14:00:28 -04:00
stats: c.object {additionalProperties: false},
gamesCompleted: c.int()
articleEdits: c.int()
levelEdits: c.int()
levelSystemEdits: c.int()
levelComponentEdits: c.int()
thangTypeEdits: c.int()
patchesSubmitted: c.int
2014-07-23 14:00:28 -04:00
description: 'Amount of patches submitted, not necessarily accepted'
patchesContributed: c.int
2014-07-23 14:00:28 -04:00
description: 'Amount of patches submitted and accepted'
patchesAccepted: c.int
2014-07-23 14:00:28 -04:00
description: 'Amount of patches accepted by the user as owner'
# The below patches only apply to those that actually got accepted
totalTranslationPatches: c.int()
totalMiscPatches: c.int()
articleTranslationPatches: c.int()
articleMiscPatches: c.int()
levelTranslationPatches: c.int()
levelMiscPatches: c.int()
levelComponentTranslationPatches: c.int()
levelComponentMiscPatches: c.int()
levelSystemTranslationPatches: c.int()
levelSystemMiscPatches: c.int()
thangTypeTranslationPatches: c.int()
thangTypeMiscPatches: c.int()
achievementTranslationPatches: c.int()
achievementMiscPatches: c.int()
pollTranslationPatches: c.int()
pollMiscPatches: c.int()
campaignTranslationPatches: c.int()
campaignMiscPatches: c.int()
courseTranslationPatches: c.int()
courseMiscPatches: c.int()
courseEdits: c.int()
concepts: {type: 'object', additionalProperties: c.int(), description: 'Number of levels completed using each programming concept.'}
2014-07-23 14:00:28 -04:00
earned: c.RewardSchema 'earned by achievements'
purchased: c.RewardSchema 'purchased with gems or money'
2015-02-24 11:54:30 -05:00
deleted: {type: 'boolean'}
2015-06-18 18:17:56 -04:00
dateDeleted: c.date()
spent: {type: 'number'}
stripeCustomerID: { type: 'string' } # TODO: Migrate away from this property
stripe: c.object {}, {
customerID: { type: 'string' }
2015-03-13 18:19:20 -04:00
planID: { enum: ['basic'], description: 'Determines if a user has or wants to subscribe' }
subscriptionID: { type: 'string', description: 'Determines if a user is subscribed' }
2014-12-02 23:01:35 -05:00
token: { type: 'string' }
couponID: { type: 'string' }
2015-03-13 18:19:20 -04:00
free: { type: ['boolean', 'string'], format: 'date-time', description: 'Type string is subscription end date' }
prepaidCode: c.shortString description: 'Prepaid code to apply to sub purchase'
2015-03-13 18:19:20 -04:00
# Sponsored subscriptions
subscribeEmails: c.array { description: 'Input for subscribing other users' }, c.shortString()
unsubscribeEmail: { type: 'string', description: 'Input for unsubscribing a sponsored user' }
recipients: c.array { title: 'Recipient subscriptions owned by this user' },
c.object { required: ['userID', 'subscriptionID'] },
userID: c.objectId { description: 'User ID of recipient' }
subscriptionID: { type: 'string' }
couponID: { type: 'string' }
sponsorID: c.objectId { description: "User ID that owns this user's subscription" }
sponsorSubscriptionID: { type: 'string', description: 'Sponsor aggregate subscription used to pay for all recipient subs' }
}
2014-04-25 13:46:43 -04:00
siteref: { type: 'string' }
referrer: { type: 'string' }
chinaVersion: { type: 'boolean' } # Old, can be removed after we make sure it's deleted from all users
country: { type: 'string', enum: ['brazil', 'china'] } # New, supports multiple countries for different versions--only set for specific countries where we have premium servers right now
2015-04-02 20:00:28 -04:00
clans: c.array {}, c.objectId()
courseInstances: c.array {}, c.objectId()
currentCourse: c.object {}, { # Old, can be removed after we deploy and delete it from all users
courseID: c.objectId({})
courseInstanceID: c.objectId({})
}
coursePrepaidID: c.objectId({
description: 'Prepaid which has paid for this user\'s course access'
})
coursePrepaid: {
type: 'object'
properties: {
_id: c.objectId()
startDate: c.stringDate()
endDate: c.stringDate()
}
}
enrollmentRequestSent: { type: 'boolean' }
2016-05-30 20:08:11 -04:00
2015-12-01 20:32:02 -05:00
schoolName: {type: 'string'}
role: {type: 'string', enum: ["God", "advisor", "parent", "principal", "student", "superintendent", "teacher", "technology coordinator"]}
birthday: c.stringDate({title: "Birthday"})
lastAchievementChecked: c.stringDate({ name: 'Last Achievement Checked' })
2015-04-02 20:00:28 -04:00
2014-04-12 04:35:56 -04:00
c.extendBasicProperties UserSchema, 'user'
UserSchema.definitions =
emailSubscription: c.object { default: { enabled: true, count: 0 } }, {
enabled: {type: 'boolean'}
lastSent: c.date()
count: {type: 'integer'}
}
2014-04-12 04:35:56 -04:00
module.exports = UserSchema