├── server ├── api │ ├── .gitkeep │ ├── email │ │ └── config │ │ │ └── routes.json │ ├── day │ │ ├── services │ │ │ └── day.js │ │ ├── models │ │ │ ├── day.js │ │ │ └── day.settings.json │ │ └── config │ │ │ └── routes.json │ ├── grade │ │ ├── models │ │ │ ├── grade.js │ │ │ └── grade.settings.json │ │ ├── services │ │ │ └── grade.js │ │ ├── controllers │ │ │ └── grade.js │ │ └── config │ │ │ └── routes.json │ ├── save │ │ ├── models │ │ │ ├── save.js │ │ │ └── save.settings.json │ │ ├── services │ │ │ └── save.js │ │ └── config │ │ │ └── routes.json │ ├── unit │ │ ├── models │ │ │ ├── unit.js │ │ │ └── unit.settings.json │ │ ├── services │ │ │ └── unit.js │ │ ├── config │ │ │ └── routes.json │ │ └── controllers │ │ │ └── unit.js │ ├── mentor │ │ ├── models │ │ │ ├── mentor.js │ │ │ └── mentor.settings.json │ │ ├── services │ │ │ └── mentor.js │ │ ├── config │ │ │ └── routes.json │ │ └── controllers │ │ │ └── mentor.js │ ├── school │ │ ├── models │ │ │ ├── school.js │ │ │ └── school.settings.json │ │ ├── services │ │ │ └── school.js │ │ ├── controllers │ │ │ └── school.js │ │ └── config │ │ │ └── routes.json │ ├── session │ │ ├── models │ │ │ ├── session.js │ │ │ └── session.settings.json │ │ ├── services │ │ │ └── session.js │ │ ├── controllers │ │ │ └── session.js │ │ └── config │ │ │ └── routes.json │ ├── student │ │ ├── models │ │ │ ├── student.js │ │ │ └── student.settings.json │ │ ├── services │ │ │ └── student.js │ │ └── config │ │ │ └── routes.json │ ├── classroom │ │ ├── models │ │ │ ├── classroom.js │ │ │ └── classroom.settings.json │ │ └── services │ │ │ └── classroom.js │ ├── selection │ │ ├── models │ │ │ ├── selection.js │ │ │ └── selection.settings.json │ │ ├── services │ │ │ └── selection.js │ │ └── config │ │ │ └── routes.json │ ├── submission │ │ ├── models │ │ │ ├── submission.js │ │ │ └── submission.settings.json │ │ └── config │ │ │ └── routes.json │ ├── block │ │ ├── controllers │ │ │ └── block.js │ │ ├── models │ │ │ ├── block.settings.json │ │ │ └── block.js │ │ ├── config │ │ │ └── routes.json │ │ └── services │ │ │ └── block.js │ ├── learning-standard │ │ ├── models │ │ │ ├── learning-standard.js │ │ │ └── learning-standard.settings.json │ │ ├── services │ │ │ └── learning-standard.js │ │ ├── controllers │ │ │ └── learning-standard.js │ │ └── config │ │ │ └── routes.json │ ├── blocks-category │ │ ├── services │ │ │ └── blocks-category.js │ │ ├── controllers │ │ │ └── blocks-category.js │ │ ├── models │ │ │ ├── blocks-category.settings.json │ │ │ └── blocks-category.js │ │ └── config │ │ │ └── routes.json │ ├── cc-workspace │ │ ├── models │ │ │ ├── cc-workspace.js │ │ │ └── cc-workspace.settings.json │ │ ├── services │ │ │ └── cc-workspace.js │ │ └── config │ │ │ ├── policies │ │ │ └── isContentCreatorOrHasClassroom.js │ │ │ └── routes.json │ ├── learning-components │ │ ├── models │ │ │ ├── learning-components.js │ │ │ └── learning-components.settings.json │ │ ├── services │ │ │ └── learning-components.js │ │ ├── controllers │ │ │ └── learning-components.js │ │ └── config │ │ │ └── routes.json │ ├── learning-component-types │ │ ├── models │ │ │ ├── learning-component-types.js │ │ │ └── learning-component-types.settings.json │ │ ├── services │ │ │ └── learning-component-types.js │ │ ├── controllers │ │ │ └── learning-component-types.js │ │ └── config │ │ │ └── routes.json │ ├── strapiusers │ │ ├── config │ │ │ └── routes.json │ │ ├── controllers │ │ │ └── strapiusers.js │ │ └── documentation │ │ │ └── 1.0.0 │ │ │ ├── admin.json │ │ │ └── strapiusers.json │ ├── classroom-manager │ │ ├── config │ │ │ └── routes.json │ │ ├── controllers │ │ │ └── classroom-manager.js │ │ ├── services │ │ │ └── classroom-manager.js │ │ └── documentation │ │ │ └── 1.0.0 │ │ │ └── classroom-manager.json │ ├── validator │ │ └── services │ │ │ └── validator.js │ └── sandbox │ │ └── config │ │ └── routes.json ├── extensions │ ├── .gitkeep │ └── users-permissions │ │ └── config │ │ └── jwt.js ├── public │ ├── client │ │ └── .gitkeep │ └── uploads │ │ ├── .gitkeep │ │ ├── 0f298ba9ad194735911aecc1839c6097.PNG │ │ ├── 2c987c12f08042b8878b36d5c32556f7.PNG │ │ ├── 2d7ee65ba79e404ea0daa9a3c08bd0bd.jpeg │ │ ├── 3b6e19b63f0d479e970cb2f13c0c7bbb.jpeg │ │ ├── 8434130ebc364acc86de7b6f3ddf7e51.jpeg │ │ └── b5d64109ebc14297bbe269c955fe6d52.PNG ├── config │ ├── locales │ │ ├── ja_jp.json │ │ ├── cs_cz.json │ │ ├── en_us.json │ │ ├── fr_fr.json │ │ ├── it_it.json │ │ ├── tr_tr.json │ │ ├── de_de.json │ │ ├── es_es.json │ │ └── ru_ru.json │ ├── compile_queue.js │ ├── functions │ │ ├── responses │ │ │ └── 404.js │ │ ├── cron.js │ │ └── bootstrap.js │ ├── policies │ │ ├── isStudent.js │ │ ├── isContentCreator.js │ │ ├── isClassroomManager.js │ │ ├── hasClassroom.js │ │ └── hasStudentsClassroom.js │ ├── server.js │ ├── middleware.js │ ├── plugins.js │ └── database.js ├── favicon.ico ├── er_diagram.PNG ├── ._.strapi-updater.json ├── admin │ └── src │ │ ├── favicon.png │ │ ├── assets │ │ └── images │ │ │ ├── logo_slack.png │ │ │ ├── social_gh.png │ │ │ ├── logo-strapi.png │ │ │ ├── logo_github.png │ │ │ ├── logo_strapi.png │ │ │ ├── social_slack.png │ │ │ ├── banner_t-shirt.png │ │ │ ├── bg_hp_tee_shirt.png │ │ │ ├── social_medium.png │ │ │ ├── social_reddit.png │ │ │ ├── social_twitter.png │ │ │ ├── logo_stack_overflow.png │ │ │ ├── background_welcome_homepage.png │ │ │ └── logo-t-shirt.svg │ │ ├── containers │ │ └── AuthPage │ │ │ └── components │ │ │ └── Logo │ │ │ └── img.js │ │ ├── themes │ │ ├── sizes.js │ │ └── colors.js │ │ ├── index.html │ │ └── components │ │ └── LeftMenu │ │ └── LeftMenuHeader │ │ └── Wrapper.js ├── package.json └── middlewares │ └── proxy │ └── index.js ├── test ├── functional │ ├── .gitkeep │ └── contentcreatorMocks.test.js ├── performance │ ├── .gitkeep │ └── load.test.js ├── jest.config.js ├── babel.config.cjs ├── integration │ └── request.js ├── package.json └── README.md ├── client ├── .gitignore ├── public │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── robots.txt │ ├── images │ │ ├── text.png │ │ ├── io_tone.png │ │ ├── io_highlow.png │ │ ├── io_notone.png │ │ ├── io_pulsein.png │ │ ├── logic_null.png │ │ ├── math_round.png │ │ ├── math_trig.png │ │ ├── servo_read.png │ │ ├── spi_setup.png │ │ ├── text_join.png │ │ ├── text_print.png │ │ ├── time_delay.png │ │ ├── controls_for.png │ │ ├── controls_if.png │ │ ├── logic_negate.png │ │ ├── math_change.png │ │ ├── math_modulo.png │ │ ├── math_number.png │ │ ├── math_on_list.png │ │ ├── math_single.png │ │ ├── serial_print.png │ │ ├── serial_setup.png │ │ ├── servo_write.png │ │ ├── spi_transfer.png │ │ ├── stepper_step.png │ │ ├── text_append.png │ │ ├── text_char_At.png │ │ ├── text_length.png │ │ ├── time_micros.png │ │ ├── time_millis.png │ │ ├── block_comment.png │ │ ├── controls_repeat.png │ │ ├── infinite_loop.png │ │ ├── insert_comment.png │ │ ├── io_analogread.png │ │ ├── io_analogwrite.png │ │ ├── io_builtin_led.png │ │ ├── io_digitalread.png │ │ ├── io_digitalwrite.png │ │ ├── io_pulsetimeout.png │ │ ├── logic_boolean.png │ │ ├── logic_compare.png │ │ ├── logic_operation.png │ │ ├── logic_ternary.png │ │ ├── math_arithmetic.png │ │ ├── math_constant.png │ │ ├── math_constrain.png │ │ ├── math_random_int.png │ │ ├── stepper_config.png │ │ ├── text_is_Empty.png │ │ ├── text_prompt_ext.png │ │ ├── variables_get.png │ │ ├── variables_set.png │ │ ├── arduino_functions.png │ │ ├── controls_for_Each.png │ │ ├── math_random_float.png │ │ ├── time_delaymicros.png │ │ ├── controls_while_Until.png │ │ ├── math_number_property.png │ │ ├── procedures_defreturn.png │ │ ├── procedures_ifreturn.png │ │ ├── sensor_set_dht_pin.png │ │ ├── spi_transfer_return.png │ │ ├── text_get_Substring.png │ │ ├── variables_set_type.png │ │ ├── procedures_callreturn.png │ │ ├── procedures_defnoreturn.png │ │ ├── small_io_pulsetimeout.png │ │ ├── controls_flow_statements.png │ │ ├── procedures_callnoreturn.png │ │ ├── sensor_read_dht_humidity.png │ │ ├── small_text_get_Substring.png │ │ └── sensor_read_dht_temperature.png │ ├── lib │ │ ├── depreciated.js │ │ └── readme.md │ └── manifest.json ├── src │ ├── assets │ │ ├── maker.png │ │ ├── arduino.png │ │ ├── nsf_logo.png │ │ ├── science.png │ │ ├── uf_logo.png │ │ ├── casmm_logo.png │ │ ├── tamu_logo.png │ │ ├── style.css │ │ └── style.less │ ├── Utils │ │ ├── hosts.js │ │ ├── PrivateRoute.jsx │ │ ├── userState.js │ │ └── AuthRequests.js │ ├── components │ │ ├── Message.jsx │ │ ├── RouteButton │ │ │ ├── RouteButton.less │ │ │ └── RouteButton.jsx │ │ ├── DayPanels │ │ │ └── BlocklyCanvasPanel │ │ │ │ ├── Icons │ │ │ │ └── textIcon.json │ │ │ │ ├── BlocklyCanvasPanel.jsx │ │ │ │ └── modals │ │ │ │ └── CodeModal.jsx │ │ ├── NavBar │ │ │ ├── NavBarConfig.json │ │ │ └── NavBar.less │ │ └── MentorSubHeader │ │ │ ├── MentorSubHeader.less │ │ │ └── MentorSubHeader.jsx │ ├── index.css │ ├── views │ │ ├── NotFound.jsx │ │ ├── UploadBlocks │ │ │ └── UpbloadBlocks.less │ │ ├── NotChrome.jsx │ │ ├── Home │ │ │ ├── Home.jsx │ │ │ └── HomeJoin.jsx │ │ ├── ContentCreator │ │ │ ├── ContentCreator.less │ │ │ ├── UnitEditor │ │ │ │ └── UnitEditor.less │ │ │ ├── UnitCreator │ │ │ │ └── UnitCreator.less │ │ │ └── LearningStandardCreator │ │ │ │ └── LearningStandardCreator.less │ │ ├── Mentor │ │ │ ├── Classroom │ │ │ │ ├── Classroom.less │ │ │ │ ├── Home │ │ │ │ │ ├── DisplayCodeModal.jsx │ │ │ │ │ ├── DisplayFormModal.jsx │ │ │ │ │ └── LearningStandardSelect │ │ │ │ │ │ └── CheckUnits.jsx │ │ │ │ ├── Roster │ │ │ │ │ ├── AddStudents │ │ │ │ │ │ ├── AddStudents.less │ │ │ │ │ │ └── AddStudentsModal.jsx │ │ │ │ │ ├── StudentModal.jsx │ │ │ │ │ └── CardView.jsx │ │ │ │ └── Classroom.jsx │ │ │ └── Dashboard │ │ │ │ └── DashboardDisplayCodeModal.jsx │ │ ├── Researcher │ │ │ ├── GroupReport.less │ │ │ ├── GroupReport.jsx │ │ │ └── Report.jsx │ │ ├── Student │ │ │ ├── form.less │ │ │ ├── form.jsx │ │ │ └── Student.less │ │ ├── TeacherLogin │ │ │ ├── Sorry.less │ │ │ ├── ConfirmEmail.jsx │ │ │ └── Sorry.jsx │ │ ├── About │ │ │ └── About.less │ │ ├── BugReport │ │ │ └── BugReport.less │ │ ├── Replay │ │ │ └── Replay.less │ │ └── Workspace │ │ │ └── Workspace.jsx │ ├── index.jsx │ └── index.less ├── package.json └── vite.config.js ├── .dockerignore ├── .gitattributes ├── compile ├── diagram.png ├── .dockerignore ├── src │ ├── utils │ │ └── base.js │ ├── cluster.js │ ├── controllers │ │ └── job.js │ └── handlers │ │ └── worker.js ├── package.json └── Dockerfile ├── package.json ├── .github ├── workflows │ ├── tag-master.yml │ ├── end-review.yml │ ├── deploy-staging.yml │ ├── update-review.yml │ ├── deploy-production.yml │ └── start-review.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── .gitignore ├── scripts └── init_db.sh ├── Dockerfile ├── docker-compose.yml └── docker-compose.test.yml /server/api/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/extensions/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/functional/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/performance/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/public/client/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/public/uploads/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | */node_modules 2 | */build 3 | */.cache 4 | */.tmp 5 | -------------------------------------------------------------------------------- /server/config/locales/ja_jp.json: -------------------------------------------------------------------------------- 1 | { 2 | "welcome": "ようこそ" 3 | } 4 | -------------------------------------------------------------------------------- /server/config/locales/cs_cz.json: -------------------------------------------------------------------------------- 1 | { 2 | "welcome": "Vítejte" 3 | } 4 | -------------------------------------------------------------------------------- /server/config/locales/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "welcome": "Welcome" 3 | } 4 | -------------------------------------------------------------------------------- /server/config/locales/fr_fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "welcome": "Bienvenue" 3 | } 4 | -------------------------------------------------------------------------------- /server/config/locales/it_it.json: -------------------------------------------------------------------------------- 1 | { 2 | "welcome": "Benvenuto" 3 | } 4 | -------------------------------------------------------------------------------- /server/config/locales/tr_tr.json: -------------------------------------------------------------------------------- 1 | { 2 | "welcome": "Hoşgeldin" 3 | } 4 | -------------------------------------------------------------------------------- /test/jest.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | testTimeout: 20000 3 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # set all bash files to keep lf endings 2 | *.sh text eol=lf -------------------------------------------------------------------------------- /server/config/locales/de_de.json: -------------------------------------------------------------------------------- 1 | { 2 | "welcome": "Willkommen" 3 | } 4 | -------------------------------------------------------------------------------- /server/config/locales/es_es.json: -------------------------------------------------------------------------------- 1 | { 2 | "welcome": "Bienvenido" 3 | } 4 | -------------------------------------------------------------------------------- /compile/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/compile/diagram.png -------------------------------------------------------------------------------- /server/config/locales/ru_ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "welcome": "Добро пожаловать" 3 | } 4 | -------------------------------------------------------------------------------- /server/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/server/favicon.ico -------------------------------------------------------------------------------- /server/er_diagram.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/server/er_diagram.PNG -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/logo192.png -------------------------------------------------------------------------------- /client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/logo512.png -------------------------------------------------------------------------------- /compile/.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore these when building the image 2 | node_modules 3 | npm-debug.log -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "react-media-recorder": "^1.7.1" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client/src/assets/maker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/src/assets/maker.png -------------------------------------------------------------------------------- /client/public/images/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/text.png -------------------------------------------------------------------------------- /client/src/assets/arduino.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/src/assets/arduino.png -------------------------------------------------------------------------------- /client/src/assets/nsf_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/src/assets/nsf_logo.png -------------------------------------------------------------------------------- /client/src/assets/science.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/src/assets/science.png -------------------------------------------------------------------------------- /client/src/assets/uf_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/src/assets/uf_logo.png -------------------------------------------------------------------------------- /server/._.strapi-updater.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/server/._.strapi-updater.json -------------------------------------------------------------------------------- /server/admin/src/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/server/admin/src/favicon.png -------------------------------------------------------------------------------- /client/public/images/io_tone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/io_tone.png -------------------------------------------------------------------------------- /client/src/assets/casmm_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/src/assets/casmm_logo.png -------------------------------------------------------------------------------- /client/src/assets/tamu_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/src/assets/tamu_logo.png -------------------------------------------------------------------------------- /client/public/images/io_highlow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/io_highlow.png -------------------------------------------------------------------------------- /client/public/images/io_notone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/io_notone.png -------------------------------------------------------------------------------- /client/public/images/io_pulsein.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/io_pulsein.png -------------------------------------------------------------------------------- /client/public/images/logic_null.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/logic_null.png -------------------------------------------------------------------------------- /client/public/images/math_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/math_round.png -------------------------------------------------------------------------------- /client/public/images/math_trig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/math_trig.png -------------------------------------------------------------------------------- /client/public/images/servo_read.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/servo_read.png -------------------------------------------------------------------------------- /client/public/images/spi_setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/spi_setup.png -------------------------------------------------------------------------------- /client/public/images/text_join.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/text_join.png -------------------------------------------------------------------------------- /client/public/images/text_print.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/text_print.png -------------------------------------------------------------------------------- /client/public/images/time_delay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/time_delay.png -------------------------------------------------------------------------------- /client/public/images/controls_for.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/controls_for.png -------------------------------------------------------------------------------- /client/public/images/controls_if.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/controls_if.png -------------------------------------------------------------------------------- /client/public/images/logic_negate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/logic_negate.png -------------------------------------------------------------------------------- /client/public/images/math_change.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/math_change.png -------------------------------------------------------------------------------- /client/public/images/math_modulo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/math_modulo.png -------------------------------------------------------------------------------- /client/public/images/math_number.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/math_number.png -------------------------------------------------------------------------------- /client/public/images/math_on_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/math_on_list.png -------------------------------------------------------------------------------- /client/public/images/math_single.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/math_single.png -------------------------------------------------------------------------------- /client/public/images/serial_print.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/serial_print.png -------------------------------------------------------------------------------- /client/public/images/serial_setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/serial_setup.png -------------------------------------------------------------------------------- /client/public/images/servo_write.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/servo_write.png -------------------------------------------------------------------------------- /client/public/images/spi_transfer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/spi_transfer.png -------------------------------------------------------------------------------- /client/public/images/stepper_step.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/stepper_step.png -------------------------------------------------------------------------------- /client/public/images/text_append.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/text_append.png -------------------------------------------------------------------------------- /client/public/images/text_char_At.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/text_char_At.png -------------------------------------------------------------------------------- /client/public/images/text_length.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/text_length.png -------------------------------------------------------------------------------- /client/public/images/time_micros.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/time_micros.png -------------------------------------------------------------------------------- /client/public/images/time_millis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/time_millis.png -------------------------------------------------------------------------------- /client/public/images/block_comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/block_comment.png -------------------------------------------------------------------------------- /client/public/images/controls_repeat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/controls_repeat.png -------------------------------------------------------------------------------- /client/public/images/infinite_loop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/infinite_loop.png -------------------------------------------------------------------------------- /client/public/images/insert_comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/insert_comment.png -------------------------------------------------------------------------------- /client/public/images/io_analogread.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/io_analogread.png -------------------------------------------------------------------------------- /client/public/images/io_analogwrite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/io_analogwrite.png -------------------------------------------------------------------------------- /client/public/images/io_builtin_led.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/io_builtin_led.png -------------------------------------------------------------------------------- /client/public/images/io_digitalread.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/io_digitalread.png -------------------------------------------------------------------------------- /client/public/images/io_digitalwrite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/io_digitalwrite.png -------------------------------------------------------------------------------- /client/public/images/io_pulsetimeout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/io_pulsetimeout.png -------------------------------------------------------------------------------- /client/public/images/logic_boolean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/logic_boolean.png -------------------------------------------------------------------------------- /client/public/images/logic_compare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/logic_compare.png -------------------------------------------------------------------------------- /client/public/images/logic_operation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/logic_operation.png -------------------------------------------------------------------------------- /client/public/images/logic_ternary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/logic_ternary.png -------------------------------------------------------------------------------- /client/public/images/math_arithmetic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/math_arithmetic.png -------------------------------------------------------------------------------- /client/public/images/math_constant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/math_constant.png -------------------------------------------------------------------------------- /client/public/images/math_constrain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/math_constrain.png -------------------------------------------------------------------------------- /client/public/images/math_random_int.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/math_random_int.png -------------------------------------------------------------------------------- /client/public/images/stepper_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/stepper_config.png -------------------------------------------------------------------------------- /client/public/images/text_is_Empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/text_is_Empty.png -------------------------------------------------------------------------------- /client/public/images/text_prompt_ext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/text_prompt_ext.png -------------------------------------------------------------------------------- /client/public/images/variables_get.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/variables_get.png -------------------------------------------------------------------------------- /client/public/images/variables_set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/variables_set.png -------------------------------------------------------------------------------- /client/public/images/arduino_functions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/arduino_functions.png -------------------------------------------------------------------------------- /client/public/images/controls_for_Each.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/controls_for_Each.png -------------------------------------------------------------------------------- /client/public/images/math_random_float.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/math_random_float.png -------------------------------------------------------------------------------- /client/public/images/time_delaymicros.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/time_delaymicros.png -------------------------------------------------------------------------------- /server/config/compile_queue.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ env }) => ({ 2 | url: env('REDIS_URL', 'redis://compile_queue:6379') 3 | }) -------------------------------------------------------------------------------- /client/public/images/controls_while_Until.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/controls_while_Until.png -------------------------------------------------------------------------------- /client/public/images/math_number_property.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/math_number_property.png -------------------------------------------------------------------------------- /client/public/images/procedures_defreturn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/procedures_defreturn.png -------------------------------------------------------------------------------- /client/public/images/procedures_ifreturn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/procedures_ifreturn.png -------------------------------------------------------------------------------- /client/public/images/sensor_set_dht_pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/sensor_set_dht_pin.png -------------------------------------------------------------------------------- /client/public/images/spi_transfer_return.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/spi_transfer_return.png -------------------------------------------------------------------------------- /client/public/images/text_get_Substring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/text_get_Substring.png -------------------------------------------------------------------------------- /client/public/images/variables_set_type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/variables_set_type.png -------------------------------------------------------------------------------- /server/admin/src/assets/images/logo_slack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/server/admin/src/assets/images/logo_slack.png -------------------------------------------------------------------------------- /server/admin/src/assets/images/social_gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/server/admin/src/assets/images/social_gh.png -------------------------------------------------------------------------------- /client/public/images/procedures_callreturn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/procedures_callreturn.png -------------------------------------------------------------------------------- /client/public/images/procedures_defnoreturn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/procedures_defnoreturn.png -------------------------------------------------------------------------------- /client/public/images/small_io_pulsetimeout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/small_io_pulsetimeout.png -------------------------------------------------------------------------------- /server/admin/src/assets/images/logo-strapi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/server/admin/src/assets/images/logo-strapi.png -------------------------------------------------------------------------------- /server/admin/src/assets/images/logo_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/server/admin/src/assets/images/logo_github.png -------------------------------------------------------------------------------- /server/admin/src/assets/images/logo_strapi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/server/admin/src/assets/images/logo_strapi.png -------------------------------------------------------------------------------- /server/admin/src/assets/images/social_slack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/server/admin/src/assets/images/social_slack.png -------------------------------------------------------------------------------- /client/public/images/controls_flow_statements.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/controls_flow_statements.png -------------------------------------------------------------------------------- /client/public/images/procedures_callnoreturn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/procedures_callnoreturn.png -------------------------------------------------------------------------------- /client/public/images/sensor_read_dht_humidity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/sensor_read_dht_humidity.png -------------------------------------------------------------------------------- /client/public/images/small_text_get_Substring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/small_text_get_Substring.png -------------------------------------------------------------------------------- /server/admin/src/assets/images/banner_t-shirt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/server/admin/src/assets/images/banner_t-shirt.png -------------------------------------------------------------------------------- /server/admin/src/assets/images/bg_hp_tee_shirt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/server/admin/src/assets/images/bg_hp_tee_shirt.png -------------------------------------------------------------------------------- /server/admin/src/assets/images/social_medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/server/admin/src/assets/images/social_medium.png -------------------------------------------------------------------------------- /server/admin/src/assets/images/social_reddit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/server/admin/src/assets/images/social_reddit.png -------------------------------------------------------------------------------- /server/admin/src/assets/images/social_twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/server/admin/src/assets/images/social_twitter.png -------------------------------------------------------------------------------- /client/public/images/sensor_read_dht_temperature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/client/public/images/sensor_read_dht_temperature.png -------------------------------------------------------------------------------- /server/admin/src/assets/images/logo_stack_overflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/server/admin/src/assets/images/logo_stack_overflow.png -------------------------------------------------------------------------------- /server/extensions/users-permissions/config/jwt.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | jwtSecret: process.env.JWT_SECRET || '58cb969b-bb0e-4492-9d8f-1306100e1f90' 3 | }; -------------------------------------------------------------------------------- /server/public/uploads/0f298ba9ad194735911aecc1839c6097.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/server/public/uploads/0f298ba9ad194735911aecc1839c6097.PNG -------------------------------------------------------------------------------- /server/public/uploads/2c987c12f08042b8878b36d5c32556f7.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/server/public/uploads/2c987c12f08042b8878b36d5c32556f7.PNG -------------------------------------------------------------------------------- /server/public/uploads/2d7ee65ba79e404ea0daa9a3c08bd0bd.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/server/public/uploads/2d7ee65ba79e404ea0daa9a3c08bd0bd.jpeg -------------------------------------------------------------------------------- /server/public/uploads/3b6e19b63f0d479e970cb2f13c0c7bbb.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/server/public/uploads/3b6e19b63f0d479e970cb2f13c0c7bbb.jpeg -------------------------------------------------------------------------------- /server/public/uploads/8434130ebc364acc86de7b6f3ddf7e51.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/server/public/uploads/8434130ebc364acc86de7b6f3ddf7e51.jpeg -------------------------------------------------------------------------------- /server/public/uploads/b5d64109ebc14297bbe269c955fe6d52.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/server/public/uploads/b5d64109ebc14297bbe269c955fe6d52.PNG -------------------------------------------------------------------------------- /server/admin/src/assets/images/background_welcome_homepage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEM-C/CaSMM/HEAD/server/admin/src/assets/images/background_welcome_homepage.png -------------------------------------------------------------------------------- /server/config/functions/responses/404.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = async (/* ctx */) => { 4 | // return ctx.notFound('My custom message 404'); 5 | }; 6 | -------------------------------------------------------------------------------- /client/src/Utils/hosts.js: -------------------------------------------------------------------------------- 1 | // get the hostname 2 | const { hostname } = window.location 3 | 4 | // export the server host 5 | export const server = hostname.includes('localhost') ? 'http://localhost:1337/api' : '/api' -------------------------------------------------------------------------------- /server/api/email/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "POST", 5 | "path": "/bug-report", 6 | "handler": "Email.send", 7 | "config": {} 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /server/api/day/services/day.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/services.html#core-services) 5 | * to customize this service 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/day/models/day.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/models.html#life-cycle-callbacks) 5 | * to customize this model 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/grade/models/grade.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/models.html#life-cycle-callbacks) 5 | * to customize this model 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/grade/services/grade.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/services.html#core-services) 5 | * to customize this service 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/save/models/save.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/models.html#life-cycle-callbacks) 5 | * to customize this model 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/save/services/save.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/services.html#core-services) 5 | * to customize this service 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/unit/models/unit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/models.html#life-cycle-callbacks) 5 | * to customize this model 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/unit/services/unit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/services.html#core-services) 5 | * to customize this service 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/mentor/models/mentor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/models.html#life-cycle-callbacks) 5 | * to customize this model 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/mentor/services/mentor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/services.html#core-services) 5 | * to customize this service 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/school/models/school.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/models.html#life-cycle-callbacks) 5 | * to customize this model 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/school/services/school.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/services.html#core-services) 5 | * to customize this service 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/session/models/session.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/models.html#life-cycle-callbacks) 5 | * to customize this model 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/student/models/student.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/models.html#life-cycle-callbacks) 5 | * to customize this model 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/student/services/student.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/services.html#core-services) 5 | * to customize this service 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /test/babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current' 8 | } 9 | } 10 | ] 11 | ] 12 | } -------------------------------------------------------------------------------- /server/api/classroom/models/classroom.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/models.html#life-cycle-callbacks) 5 | * to customize this model 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/selection/models/selection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/models.html#life-cycle-callbacks) 5 | * to customize this model 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/submission/models/submission.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/models.html#life-cycle-callbacks) 5 | * to customize this model 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /compile/src/utils/base.js: -------------------------------------------------------------------------------- 1 | module.exports.compileLog = (message) => { 2 | 3 | // construct log 4 | const logPrefix = `[${(new Date()).toISOString()}]` 5 | const log = `${logPrefix} ${message}` 6 | 7 | // output 8 | console.log(log) 9 | } -------------------------------------------------------------------------------- /server/api/grade/controllers/grade.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/controllers.html#core-controllers) 5 | * to customize this controller 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/school/controllers/school.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/controllers.html#core-controllers) 5 | * to customize this controller 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/block/controllers/block.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/3.0.0-beta.x/concepts/controllers.html#core-controllers) 5 | * to customize this controller 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/learning-standard/models/learning-standard.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/models.html#life-cycle-callbacks) 5 | * to customize this model 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/learning-standard/services/learning-standard.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/services.html#core-services) 5 | * to customize this service 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/session/services/session.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/services.html#core-services) 5 | * to customize this service 6 | */ 7 | 8 | module.exports = {}; 9 | 10 | 11 | -------------------------------------------------------------------------------- /server/api/blocks-category/services/blocks-category.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/3.0.0-beta.x/concepts/services.html#core-services) 5 | * to customize this service 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/admin/src/containers/AuthPage/components/Logo/img.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Img = styled.img` 4 | height: 100px; 5 | `; 6 | //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 7 | //Changed height to: 100px 8 | 9 | export default Img; 10 | -------------------------------------------------------------------------------- /server/api/blocks-category/controllers/blocks-category.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/3.0.0-beta.x/concepts/controllers.html#core-controllers) 5 | * to customize this controller 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/cc-workspace/models/cc-workspace.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#lifecycle-hooks) 5 | * to customize this model 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/cc-workspace/services/cc-workspace.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#core-services) 5 | * to customize this service 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/admin/src/assets/images/logo-t-shirt.svg: -------------------------------------------------------------------------------- 1 | 👕 2 | -------------------------------------------------------------------------------- /server/api/learning-components/models/learning-components.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#lifecycle-hooks) 5 | * to customize this model 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/learning-components/services/learning-components.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#core-services) 5 | * to customize this service 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/learning-component-types/models/learning-component-types.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#lifecycle-hooks) 5 | * to customize this model 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/learning-component-types/services/learning-component-types.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#core-services) 5 | * to customize this service 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/learning-components/controllers/learning-components.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#core-controllers) 5 | * to customize this controller 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/strapiusers/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/strapiusers/super-admin", 6 | "handler": "strapiusers.findSuperAdmins", 7 | "config": { 8 | "policies": [] 9 | } 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /client/src/components/Message.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Alert } from 'antd'; 3 | 4 | const Message = ({ type, message }) => { 5 | return ; 6 | }; 7 | 8 | Message.defaultProps = { 9 | type: 'error', 10 | }; 11 | 12 | export default Message; 13 | -------------------------------------------------------------------------------- /server/api/learning-component-types/controllers/learning-component-types.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#core-controllers) 5 | * to customize this controller 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /server/api/classroom-manager/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/classroom-managers/me", 6 | "handler": "classroom-manager.me", 7 | "config": { 8 | "policies": ["global::isClassroomManager"] 9 | } 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /server/config/policies/isStudent.js: -------------------------------------------------------------------------------- 1 | // 2 | // Check if the current user is a student 3 | // 4 | module.exports = async (ctx, next) => { 5 | if (ctx.state.user && ctx.state.user.isStudent) { 6 | // Go to next policy or controller 7 | return await next() 8 | } 9 | 10 | ctx.unauthorized(`You're not allowed to perform this action!`) 11 | } -------------------------------------------------------------------------------- /client/src/components/RouteButton/RouteButton.less: -------------------------------------------------------------------------------- 1 | @import "../../assets/style.less"; 2 | 3 | #route-button { 4 | height: 250px; 5 | padding: 10px 20px; 6 | border-radius: 5px; 7 | font-size: 90px; 8 | font-weight: 800; 9 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; 10 | margin: 150px 80px; 11 | } 12 | -------------------------------------------------------------------------------- /client/src/Utils/PrivateRoute.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Navigate } from 'react-router-dom'; 3 | import { getToken } from './AuthRequests'; 4 | 5 | // creates private route handler 6 | function PrivateRoute({ children }) { 7 | const token = getToken(); 8 | return token ? children : ; 9 | } 10 | 11 | export default PrivateRoute; 12 | -------------------------------------------------------------------------------- /test/performance/load.test.js: -------------------------------------------------------------------------------- 1 | import http from 'k6/http' 2 | import { sleep } from 'k6' 3 | 4 | const host = 'http://localhost:1337' 5 | 6 | export const options = { 7 | duration: '0.1m', 8 | vus: 50, 9 | thresholds: { 10 | http_req_duration: ['p(95)<2500'], 11 | }, 12 | } 13 | 14 | export default function () { 15 | http.get(`${host}/topics`) 16 | sleep(1) 17 | } -------------------------------------------------------------------------------- /server/config/server.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ env }) => ({ 2 | host: env('HOST', '0.0.0.0'), 3 | port: env.int('PORT', 1337), 4 | // url: 'localhost:1337', 5 | admin: { 6 | auth: { 7 | secret: env( 8 | 'ADMIN_JWT_SECRET', 9 | process.env.ADMIN_JWT_TOKEN || 10 | 'fd6af0fac1067asfasf0ef12AGWADGJe9d518d1604298' 11 | ), 12 | }, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /server/config/policies/isContentCreator.js: -------------------------------------------------------------------------------- 1 | // 2 | // Check if the current user is a classroom manager 3 | // 4 | module.exports = async (ctx, next) => { 5 | if (ctx.state.user && ctx.state.user.role.name === 'Content Creator') { 6 | // Go to next policy or controller 7 | return await next() 8 | } 9 | 10 | ctx.unauthorized(`You're not allowed to perform this action!`) 11 | } -------------------------------------------------------------------------------- /.github/workflows/tag-master.yml: -------------------------------------------------------------------------------- 1 | name: Tag New Master Release 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | types: [closed] 7 | jobs: 8 | run: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | - name: Tag Master 14 | uses: STEM-C/auto/tag@v0.7.2 15 | with: 16 | repo_token: '${{ secrets.GITHUB_TOKEN }}' 17 | -------------------------------------------------------------------------------- /compile/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "compile", 3 | "version": "0.0.1", 4 | "description": "Arduino compilier service", 5 | "author": "Nicholas Ionata", 6 | "license": "MIT", 7 | "dependencies": { 8 | "body-parser": "^1.18.2", 9 | "bull": "^4.9.0", 10 | "express": "^4.16.2", 11 | "properties": "^1.2.1", 12 | "redis-url-parse": "^2.0.0", 13 | "throng": "^5.0.0", 14 | "tmp": "0.2.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /client/src/components/RouteButton/RouteButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import './RouteButton.less'; 4 | 5 | export default function RouteButton({ link, id, size, variant, children }) { 6 | return ( 7 | 8 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /client/src/views/NotFound.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const NotFound = () => { 4 | return ( 5 |
6 |

7 | 404 8 |

9 |

10 | Page Not Found 11 |

12 |
13 | ) 14 | }; 15 | 16 | export default NotFound; 17 | -------------------------------------------------------------------------------- /test/integration/request.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import axios from 'axios' 4 | //axios.defaults.adapter = require('axios/lib/adapters/http'); 5 | const host = 'http://localhost:1337/api' 6 | 7 | export const getPublicRequestModule = () => axios.create({ 8 | baseURL: host 9 | }) 10 | 11 | export const getAuthorizedRequestModule = (token) => axios.create({ 12 | baseURL: host, 13 | headers: { 14 | Authorization: `Bearer ${token}` 15 | } 16 | }) -------------------------------------------------------------------------------- /server/config/policies/isClassroomManager.js: -------------------------------------------------------------------------------- 1 | // 2 | // Check if the current user is a classroom manager 3 | // 4 | module.exports = async (ctx, next) => { 5 | if ( 6 | (ctx.state.user && ctx.state.user.role.name === 'Classroom Manager') || 7 | ctx.state.user.role.name === 'Researcher' 8 | ) { 9 | // Go to next policy or controller 10 | return await next(); 11 | } 12 | 13 | ctx.unauthorized(`You're not allowed to perform this action!`); 14 | }; 15 | -------------------------------------------------------------------------------- /client/src/views/UploadBlocks/UpbloadBlocks.less: -------------------------------------------------------------------------------- 1 | @import '../../assets/style.less'; 2 | 3 | #main-header { 4 | text { 5 | color: white; 6 | } 7 | a:active { 8 | color: white; 9 | background: transparent; 10 | } 11 | a:link { 12 | color: white; 13 | background: transparent; 14 | } 15 | a:hover { 16 | color: white; 17 | background: transparent; 18 | } 19 | a:visited { 20 | color: white; 21 | background: transparent; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/end-review.yml: -------------------------------------------------------------------------------- 1 | name: Delete Review App 2 | on: 3 | pull_request: 4 | branches: [develop] 5 | types: [closed] 6 | jobs: 7 | run: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Delete review app 12 | id: app 13 | uses: STEM-C/auto/review@v0.7.2 14 | with: 15 | base: review 16 | pipeline: ${{ secrets.PIPELINE_ID }} 17 | token: ${{ secrets.HEROKU_TOKEN }} 18 | -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@babel/preset-env": "^7.10.4", 4 | "axios": "^0.19.2", 5 | "babel-jest": "^26.1.0", 6 | "jest": "^26.6.3", 7 | "k6": "^0.0.0" 8 | }, 9 | "scripts": { 10 | "start": "docker-compose -f ../docker-compose.test.yml up", 11 | "functional": "jest ./functional/*.test.js", 12 | "integration": "jest ./integration/*.test.js", 13 | "performance": "k6 run ./performance/*.test.js" 14 | }, 15 | "type": "module" 16 | } 17 | -------------------------------------------------------------------------------- /server/api/selection/services/selection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/services.html#core-services) 5 | * to customize this service 6 | */ 7 | 8 | module.exports.findCurrSelection = async (classroomId) => { 9 | const selection = await strapi 10 | .query('selection') 11 | .findOne({ current: true, classroom: classroomId, _sort: 'id:desc' }, [ 12 | 'learning_standard', 13 | ]); 14 | return selection; 15 | }; 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #configuration ignores 2 | .idea/ 3 | .vscode/ 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | 12 | # Environment 13 | .env 14 | *.sql 15 | .cache/ 16 | *.dump 17 | !blocks_db.dump 18 | !development_db.dump 19 | !test_db.dump 20 | .strapi-updater.json 21 | 22 | # Packages 23 | *.zip 24 | *.tar 25 | *.gz 26 | 27 | # OS 28 | .DS_Store 29 | .tmp 30 | 31 | # node 32 | node_modules/ 33 | build/ 34 | server/public/client/* 35 | !server/public/client/.gitkeep -------------------------------------------------------------------------------- /client/src/views/NotChrome.jsx: -------------------------------------------------------------------------------- 1 | const NotFound = () => { 2 | return ( 3 |
8 |

9 | Please use{' '} 10 | 11 | Chrome 12 | {' '} 13 | browser to run CaSMM 14 |

15 |
16 | ); 17 | }; 18 | 19 | export default NotFound; 20 | -------------------------------------------------------------------------------- /server/api/blocks-category/models/blocks-category.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "connection": "default", 4 | "collectionName": "blocks_categories", 5 | "info": { 6 | "name": "Blocks Category" 7 | }, 8 | "options": { 9 | "increments": true, 10 | "timestamps": true 11 | }, 12 | "attributes": { 13 | "name": { 14 | "type": "string" 15 | }, 16 | "blocks": { 17 | "via": "blocks_category", 18 | "collection": "block" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /client/src/views/Home/Home.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Logo from "../../assets/casmm_logo.png"; 3 | import NavBar from "../../components/NavBar/NavBar"; 4 | import './Home.less'; 5 | import HomeJoin from "./HomeJoin"; 6 | 7 | const Home = () => ( 8 |
9 | 10 |
11 | 12 | 13 |
14 |
15 | ) 16 | 17 | export default Home; -------------------------------------------------------------------------------- /compile/src/cluster.js: -------------------------------------------------------------------------------- 1 | const throng = require('throng') 2 | const { init, start } = require('./handlers/worker') 3 | 4 | // Spin up multiple processes to handle jobs to take advantage of more CPU cores 5 | // See: https://devcenter.heroku.com/articles/node-concurrency for more info 6 | let workers = process.env.WEB_CONCURRENCY || 1 7 | 8 | // Initialize the clustered worker process 9 | // See: https://devcenter.heroku.com/articles/node-concurrency for more info 10 | throng({ 11 | workers, 12 | master: init, 13 | start 14 | }) -------------------------------------------------------------------------------- /server/admin/src/themes/sizes.js: -------------------------------------------------------------------------------- 1 | const sizes = { 2 | borderRadius: '2px', 3 | header: { 4 | height: '6rem', 5 | }, 6 | leftMenu: { 7 | height: '8rem', 8 | width: '24rem', 9 | }, 10 | margins: { 11 | // TODO: 12 | sm: '10px', 13 | }, 14 | paddings: { 15 | // TODO 16 | xs: '5px', 17 | sm: '10px', 18 | md: '30px', 19 | lg: '40px', 20 | }, 21 | fonts: { 22 | xs: '11px', 23 | sm: '12px', 24 | md: '13px', 25 | lg: '18px', 26 | }, 27 | }; 28 | 29 | export default sizes; 30 | -------------------------------------------------------------------------------- /server/config/functions/cron.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Cron config that gives you an opportunity 5 | * to run scheduled jobs. 6 | * 7 | * The cron format consists of: 8 | * [SECOND (optional)] [MINUTE] [HOUR] [DAY OF MONTH] [MONTH OF YEAR] [DAY OF WEEK] 9 | * 10 | * See more details here: https://strapi.io/documentation/3.0.0-beta.x/concepts/configurations.html#cron-tasks 11 | */ 12 | 13 | module.exports = { 14 | /** 15 | * Simple example. 16 | * Every monday at 1am. 17 | */ 18 | // '0 1 * * 1': () => { 19 | // 20 | // } 21 | }; 22 | -------------------------------------------------------------------------------- /client/src/views/ContentCreator/ContentCreator.less: -------------------------------------------------------------------------------- 1 | @import '../../assets/style.less'; 2 | #content-creator-table-container { 3 | width: 80%; 4 | margin: 2.5vh auto 0 auto; 5 | 6 | #content-creator-btn-container { 7 | display: flex; 8 | flex-direction: row; 9 | float: right; 10 | margin-bottom: 5px; 11 | z-index: 10; 12 | } 13 | 14 | .ant-table-row { 15 | #quick-look { 16 | visibility: hidden; 17 | &:hover { 18 | visibility: visible; 19 | } 20 | } 21 | } 22 | 23 | .ant-table-wrapper { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /client/public/lib/depreciated.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is used to store all the depreciated codes 3 | * still being used by our version of blockly 4 | */ 5 | 6 | var goog = goog || {}; //declare goog if not defined, else add to it 7 | 8 | goog.isArray = function (a) { return "array" == goog.typeOf(a) }; 9 | 10 | goog.isString = function(val) { 11 | return typeof val == 'string'; 12 | }; 13 | 14 | goog.isNumber = function(val) { 15 | return typeof val == 'number'; 16 | }; 17 | 18 | goog.isFunction = function(val) { 19 | return goog.typeOf(val) == 'function'; 20 | }; 21 | -------------------------------------------------------------------------------- /server/api/grade/models/grade.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "grades", 4 | "info": { 5 | "name": "grade" 6 | }, 7 | "options": { 8 | "increments": true, 9 | "timestamps": true 10 | }, 11 | "attributes": { 12 | "name": { 13 | "type": "string", 14 | "required": true, 15 | "unique": true 16 | }, 17 | "classrooms": { 18 | "via": "grade", 19 | "collection": "classroom" 20 | }, 21 | "units": { 22 | "via": "grade", 23 | "collection": "unit" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/api/learning-component-types/models/learning-component-types.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "learning_component_types", 4 | "info": { 5 | "name": "Learning Component Types" 6 | }, 7 | "options": { 8 | "increments": true, 9 | "timestamps": true, 10 | "draftAndPublish": true 11 | }, 12 | "attributes": { 13 | "name": { 14 | "type": "string" 15 | }, 16 | "learning_components": { 17 | "collection": "learning-components", 18 | "via": "learning_component_type" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /server/api/selection/models/selection.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "selections", 4 | "info": { 5 | "name": "Selection" 6 | }, 7 | "options": { 8 | "increments": true, 9 | "timestamps": true 10 | }, 11 | "attributes": { 12 | "classroom": { 13 | "via": "selections", 14 | "model": "classroom" 15 | }, 16 | "learning_standard": { 17 | "model": "learning-standard" 18 | }, 19 | "current": { 20 | "type": "boolean", 21 | "default": true, 22 | "required": true 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Client", 3 | "name": "STEM+C Client Application", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /scripts/init_db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | env=$ENVIRONMENT 6 | db_url=$DATABASE_URL 7 | script_path=$SCRIPT_PATH 8 | 9 | if [[ -n $env ]] && [[ -n $db_url ]] && [[ -n $script_path ]]; 10 | then 11 | 12 | dump="${env}_db.dump" 13 | path="$script_path/$dump" 14 | 15 | if [[ -f $path ]]; 16 | then 17 | 18 | psql "$DATABASE_URL" < "$path" 19 | echo "Successfully imported $dump" 20 | else 21 | echo "Missing $dump" 22 | fi; 23 | else 24 | echo "Missing environment variable(s):" 25 | echo "ENVIRONMENT=$env or DATABASE_URL=$db_url or SCRIPT_PATH=$script_path" 26 | fi; -------------------------------------------------------------------------------- /client/src/views/ContentCreator/UnitEditor/UnitEditor.less: -------------------------------------------------------------------------------- 1 | @import "../../../assets/style.less"; 2 | 3 | #base{ 4 | background-color: #colors[primary]; 5 | } 6 | 7 | #search-button{ 8 | left: 100px; 9 | } 10 | 11 | #list-position{ 12 | 13 | position: relative; 14 | 15 | #add-button{ 16 | position: absolute; 17 | } 18 | } 19 | 20 | #carousel{ 21 | width: '160px'; 22 | } 23 | 24 | #display-code-modal { 25 | 26 | 27 | #display-code-btn { 28 | width: 18vw; 29 | min-height: 4vh; 30 | font-size: 1em; 31 | 32 | &:hover { 33 | width: 20vw; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /client/src/index.jsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client'; 2 | import { BrowserRouter } from 'react-router-dom'; 3 | import App from './App'; 4 | import './index.less'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | const container = document.getElementById("root") 8 | createRoot(container).render( 9 | 10 | 11 | 12 | ) 13 | 14 | // If you want your app to work offline and load faster, you can change 15 | // unregister() to register() below. Note this comes with some pitfalls. 16 | // Learn more about service workers: https://bit.ly/CRA-PWA 17 | serviceWorker.unregister(); 18 | -------------------------------------------------------------------------------- /server/api/school/models/school.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "schools", 4 | "info": { 5 | "name": "School" 6 | }, 7 | "options": { 8 | "increments": true, 9 | "timestamps": true 10 | }, 11 | "attributes": { 12 | "name": { 13 | "type": "string" 14 | }, 15 | "county": { 16 | "type": "string" 17 | }, 18 | "state": { 19 | "type": "string" 20 | }, 21 | "classrooms": { 22 | "via": "school", 23 | "collection": "classroom" 24 | }, 25 | "mentors": { 26 | "via": "school", 27 | "collection": "mentor" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM strapi/base 2 | FROM node:20 as client-build 3 | 4 | WORKDIR /usr/src/app/client 5 | COPY ./client/package.json . 6 | COPY ./client/yarn.lock . 7 | RUN yarn install 8 | COPY ./client . 9 | RUN PUBLIC_URL=/client yarn build 10 | 11 | FROM node:14 as server-build 12 | WORKDIR /usr/src/app 13 | COPY ./server/package.json . 14 | COPY ./server/yarn.lock . 15 | RUN yarn install 16 | COPY ./server . 17 | ENV NODE_ENV production 18 | RUN PUBLIC_URL=/api yarn build 19 | 20 | FROM node:14 as final 21 | WORKDIR /usr/src/app 22 | COPY --from=server-build /usr/src/app ./ 23 | COPY --from=client-build /usr/src/app/client/build ./public/client 24 | 25 | CMD yarn start -------------------------------------------------------------------------------- /server/api/classroom-manager/controllers/classroom-manager.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { sanitizeEntity } = require('strapi-utils/lib'); 4 | 5 | module.exports = { 6 | /** 7 | * Get the currrent classroom manager that is logged in 8 | * 9 | * @return {Mentor} 10 | */ 11 | async me(ctx) { 12 | const { id } = ctx.state.user; 13 | 14 | // get the classroom manager 15 | const { classroomManager } = await strapi.services[ 16 | 'classroom-manager' 17 | ].findById(id); 18 | 19 | // remove private fields and return the classroom manager 20 | return sanitizeEntity(classroomManager, { model: strapi.models.mentor }); 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /client/src/index.less: -------------------------------------------------------------------------------- 1 | @import 'antd/dist/antd.less'; 2 | @import './assets/style.less'; 3 | 4 | body { 5 | margin: 0; 6 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 7 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 8 | sans-serif; 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | overflow-x: hidden !important; 12 | } 13 | 14 | code { 15 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 16 | monospace; 17 | } 18 | 19 | .blocklyTreeRow { 20 | border-radius: 4px; 21 | text-align: center; 22 | line-height: initial!important; 23 | } -------------------------------------------------------------------------------- /server/api/learning-components/models/learning-components.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "learning_components", 4 | "info": { 5 | "name": "Learning Components" 6 | }, 7 | "options": { 8 | "increments": true, 9 | "timestamps": true, 10 | "draftAndPublish": true 11 | }, 12 | "attributes": { 13 | "type": { 14 | "type": "string" 15 | }, 16 | "days": { 17 | "via": "learning_components", 18 | "collection": "day", 19 | "dominant": true 20 | }, 21 | "learning_component_type": { 22 | "via": "learning_components", 23 | "model": "learning-component-types" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/api/save/models/save.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "saves", 4 | "info": { 5 | "name": "save", 6 | "description": "" 7 | }, 8 | "options": { 9 | "increments": true, 10 | "timestamps": true, 11 | "draftAndPublish": false 12 | }, 13 | "attributes": { 14 | "day": { 15 | "model": "day" 16 | }, 17 | "student": { 18 | "model": "student" 19 | }, 20 | "workspace": { 21 | "type": "text", 22 | "required": true 23 | }, 24 | "replay": { 25 | "type": "json" 26 | }, 27 | "session": { 28 | "via": "saves", 29 | "model": "session" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /server/api/validator/services/validator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports.isInt = (object) => { 4 | return object !== undefined && Number.isInteger(object); 5 | }; 6 | 7 | module.exports.isPositiveInt = (object) => { 8 | return object !== undefined && Number.isInteger(object) && object >= 0; 9 | }; 10 | 11 | module.exports.isIntArray = (arr) => { 12 | // ensure the input is an non-empty array 13 | if (!arr || !Array.isArray(arr) || !arr.length) return false; 14 | 15 | // ensure the array contains integers 16 | const nonIntegers = arr.filter((num) => !Number.isInteger(num)); 17 | if (nonIntegers.length) return false; 18 | 19 | // passes all tests 20 | return true; 21 | }; 22 | -------------------------------------------------------------------------------- /server/api/block/models/block.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "connection": "default", 4 | "collectionName": "blocks", 5 | "info": { 6 | "name": "Block", 7 | "description": "" 8 | }, 9 | "options": { 10 | "increments": true, 11 | "timestamps": true, 12 | "draftAndPublish": false 13 | }, 14 | "attributes": { 15 | "name": { 16 | "type": "string", 17 | "required": true, 18 | "unique": true 19 | }, 20 | "description": { 21 | "type": "text" 22 | }, 23 | "blocks_category": { 24 | "via": "blocks", 25 | "model": "blocks-category" 26 | }, 27 | "image_url": { 28 | "type": "string" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Story:** 11 | A clear and concise description of the feature from the perspective of the main user. 12 | 13 | **Implementation** 14 | A clear and concise description of the feature from the developers point of view. 15 | 16 | **Client/Server/Compile/Database** 17 | Choose one of the above and break goals down into individual tasks with checkboxes next to them. One issue may include multiple tasks for multiple of the above categories 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /server/api/mentor/models/mentor.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "mentors", 4 | "info": { 5 | "name": "Mentor" 6 | }, 7 | "options": { 8 | "increments": true, 9 | "timestamps": true 10 | }, 11 | "attributes": { 12 | "first_name": { 13 | "type": "string" 14 | }, 15 | "last_name": { 16 | "type": "string" 17 | }, 18 | "school": { 19 | "model": "school", 20 | "via": "mentors" 21 | }, 22 | "classrooms": { 23 | "via": "mentors", 24 | "collection": "classroom", 25 | "dominant": true 26 | }, 27 | "user": { 28 | "plugin": "users-permissions", 29 | "model": "user" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /compile/Dockerfile: -------------------------------------------------------------------------------- 1 | # Start from the node image 2 | FROM node:latest 3 | 4 | # Create app directory 5 | WORKDIR /usr/src/app 6 | 7 | # Install app dependencies 8 | COPY package.json . 9 | COPY yarn.lock . 10 | RUN yarn 11 | 12 | # Install arduino compiler 13 | RUN curl https://downloads.arduino.cc/arduino-1.8.5-linux64.tar.xz | tar -xJ 14 | # Install the DHT library AND its dependency 15 | RUN cd arduino-1.8.5/libraries && wget -c https://codeload.github.com/adafruit/DHT-sensor-library/tar.gz/refs/tags/1.4.3 -O - | tar -xz && wget -c https://codeload.github.com/adafruit/Adafruit_Sensor/tar.gz/refs/tags/1.1.4 -O - | tar -xz 16 | 17 | # Bundle app source 18 | COPY ./src . 19 | 20 | # Run when container is started 21 | CMD node cluster.js -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # Tests 2 | 3 | > Full stack test suite 4 | 5 |
6 | 7 | ## Setup 8 | 9 | 1. Install dependencies `yarn` 10 | 11 | 2. Run the service `yarn start` 12 | 13 | > Make sure to delete the db container to make sure it rebuilds for testing 14 | 15 |
16 | 17 | ## Testing 18 | 19 | ### Functional 20 | 21 | - Framework – Jest 22 | - Usage – Test UI w/ snapshots and mocking 23 | - Run `yarn functional` 24 | 25 | ### Integration 26 | 27 | - Framework – Jest 28 | - Usage – Test all the API endpoints for roles, data access, and function 29 | - Run `yarn integration` 30 | 31 | ### Performance 32 | 33 | - Framework – K6 34 | - Usage – Stress and Load test the application 35 | - Run yarn `performance` 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /.github/workflows/deploy-staging.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Staging 2 | on: 3 | push: 4 | branches: ['release/v[0-9].[0-9]'] 5 | jobs: 6 | run: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout the repo 10 | uses: actions/checkout@v2 11 | - name: Get the version 12 | id: get_version 13 | run: echo ::set-output name=VERSION::${GITHUB_REF#refs/heads/release/} 14 | - name: Built, test, and deploy app 15 | uses: STEM-C/auto/build-test-deploy@v0.7.2 16 | with: 17 | image_tag: staging-${{ steps.get_version.outputs.VERSION }} 18 | app_name: casmm-staging 19 | github_token: ${{ secrets.GITHUB_TOKEN }} 20 | env: 21 | HEROKU_API_KEY: ${{ secrets.HEROKU_TOKEN }} 22 | -------------------------------------------------------------------------------- /server/admin/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | CaSMM Admin 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /server/api/unit/models/unit.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "units", 4 | "info": { 5 | "name": "unit" 6 | }, 7 | "options": { 8 | "increments": true, 9 | "timestamps": true 10 | }, 11 | "attributes": { 12 | "grade": { 13 | "model": "grade", 14 | "via": "units" 15 | }, 16 | "name": { 17 | "type": "string", 18 | "required": true 19 | }, 20 | "teks_id": { 21 | "type": "string" 22 | }, 23 | "teks_description": { 24 | "type": "text" 25 | }, 26 | "number": { 27 | "type": "integer", 28 | "required":true 29 | 30 | }, 31 | "learning_standards": { 32 | "via": "unit", 33 | "collection": "learning-standard" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /server/api/sandbox/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/sandbox/toolbox", 6 | "handler": "sandbox.toolbox", 7 | "config": { 8 | "policies": [] 9 | } 10 | }, 11 | { 12 | "method": "GET", 13 | "path": "/sandbox/submission/:id", 14 | "handler": "sandbox.findOneSubmission", 15 | "config": { 16 | "policies": [] 17 | } 18 | }, 19 | { 20 | "method": "POST", 21 | "path": "/sandbox/submission", 22 | "handler": "sandbox.createSubmission", 23 | "config": { 24 | "policies": [] 25 | } 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /server/api/cc-workspace/models/cc-workspace.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "cc_workspaces", 4 | "info": { 5 | "name": "CCWorkspace", 6 | "description": "" 7 | }, 8 | "options": { 9 | "increments": true, 10 | "timestamps": true, 11 | "draftAndPublish": true 12 | }, 13 | "attributes": { 14 | "name": { 15 | "type": "string", 16 | "required": true 17 | }, 18 | "description": { 19 | "type": "text" 20 | }, 21 | "template": { 22 | "type": "text", 23 | "required": true 24 | }, 25 | "blocks": { 26 | "private": true, 27 | "collection": "block" 28 | }, 29 | "classroom": { 30 | "via": "cc_workspaces", 31 | "private": true, 32 | "model": "classroom" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/update-review.yml: -------------------------------------------------------------------------------- 1 | name: Update Review App 2 | on: 3 | pull_request: 4 | branches: [develop] 5 | types: [synchronize] 6 | jobs: 7 | run: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout the repo 11 | uses: actions/checkout@v2 12 | - name: Get review app name 13 | id: get_name 14 | run: echo ::set-output name=NAME::review-pr-$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }') 15 | - name: Built, test, and deploy app 16 | uses: STEM-C/auto/build-test-deploy@v0.7.2 17 | with: 18 | image_tag: ${{ steps.get_name.outputs.NAME }} 19 | app_name: ${{ steps.get_name.outputs.NAME }} 20 | github_token: ${{ secrets.GITHUB_TOKEN }} 21 | env: 22 | HEROKU_API_KEY: ${{ secrets.HEROKU_TOKEN }} 23 | -------------------------------------------------------------------------------- /client/src/components/DayPanels/BlocklyCanvasPanel/Icons/textIcon.json: -------------------------------------------------------------------------------- 1 | { 2 | "path": "M85.83203,22.77656c-0.54801,0.01312 -1.09438,0.06551 -1.63489,0.15677h-44.0638h-5.73333c-3.1648,0 -5.73333,2.56853 -5.73333,5.73333v17.2c0,3.1648 2.56853,5.73333 5.73333,5.73333h5.73333c3.1648,0 5.73333,-2.56853 5.73333,-5.73333v-5.73333h28.66667v91.73333h-5.73333c-3.1648,0 -5.73333,2.56853 -5.73333,5.73333v5.73333c0,3.1648 2.56853,5.73333 5.73333,5.73333h15.34114c1.23099,0.20221 2.48672,0.20221 3.71771,0h15.34114c3.1648,0 5.73333,-2.56853 5.73333,-5.73333v-5.73333c0,-3.1648 -2.56853,-5.73333 -5.73333,-5.73333h-5.73333v-91.73333h28.66667v5.73333c0, 3.1648 2.56853,5.73333 5.73333,5.73333h5.73333c3.1648,0 5.73333,-2.56853 5.73333,-5.73333v-17.2c0,-3.1648 -2.56853,-5.73333 -5.73333, -5.73333h-5.73333h-44.03021c-0.66226,-0.11084 -1.33298,-0.1633 -2.00442,-0.15677z" 3 | } 4 | -------------------------------------------------------------------------------- /server/api/classroom-manager/services/classroom-manager.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // find a classroom manager by user id 4 | module.exports.findById = async (id) => { 5 | const mentor = await strapi.services.mentor.findOne({ user: id }); 6 | return { 7 | classroomManager: mentor, 8 | }; 9 | }; 10 | 11 | // find all classes for a classroom manager 12 | module.exports.findClassesById = async (id) => { 13 | const mentor = await strapi.services.mentor.findOne({ user: id }); 14 | const classrooms = mentor.classrooms; 15 | 16 | const crList = await Promise.all( 17 | classrooms.map(async (classroom) => { 18 | const model = await strapi 19 | .query('classroom') 20 | .model.where('id', classroom.id) 21 | .fetch(); 22 | return model ? model.toJSON() : undefined; 23 | }) 24 | ); 25 | 26 | return crList; 27 | }; 28 | -------------------------------------------------------------------------------- /client/src/components/NavBar/NavBarConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": { 3 | "TeacherLogin": "/teacherlogin", 4 | "Sandbox": "/sandbox", 5 | "About": "/about", 6 | "Home": "/", 7 | "Dashboard": "/dashboard", 8 | "SignOut": "/", 9 | "AccountInfo": "/account", 10 | "ContentCreatorDashboard": "/ccdashboard", 11 | "ResearcherDashboard": "/report", 12 | "BugReport": "/bugreport" 13 | }, 14 | "users": { 15 | "DefaultUser": ["Home", "TeacherLogin", "Sandbox", "About"], 16 | "Mentor": ["Dashboard", "AccountInfo", "SignOut", "Sandbox", "BugReport"], 17 | "Student": ["SignOut"], 18 | "ContentCreator": [ 19 | "ContentCreatorDashboard", 20 | "AccountInfo", 21 | "Sandbox", 22 | "SignOut", 23 | "BugReport" 24 | ], 25 | "Researcher": ["ResearcherDashboard", "BugReport", "SignOut"] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /client/src/views/Mentor/Classroom/Classroom.less: -------------------------------------------------------------------------------- 1 | @import '../../../assets/style.less'; 2 | 3 | #main-header { 4 | margin: 0 0 1vh 5vw; 5 | 6 | div { 7 | color: #colors[text-secondary]; 8 | font-weight: bolder; 9 | } 10 | #classroom { 11 | font-size: 5vh; 12 | } 13 | #code { 14 | font-size: 4vh; 15 | } 16 | } 17 | 18 | .ant-tabs-nav-wrap, 19 | .ant-tabs-nav { 20 | margin: auto 5vw; 21 | } 22 | 23 | .ant-tabs-tab { 24 | min-width: 10vw; 25 | justify-content: center; 26 | color: #colors[tertiary] !important; 27 | 28 | &:hover { 29 | color: #colors[tertiary]; 30 | opacity: 0.6; 31 | } 32 | } 33 | 34 | .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn { 35 | color: #colors[secondary] !important; 36 | 37 | &:hover { 38 | color: #colors[secondary]; 39 | opacity: 0.6; 40 | } 41 | } 42 | 43 | .ant-tabs-ink-bar { 44 | background: #colors[secondary] !important; 45 | } 46 | -------------------------------------------------------------------------------- /client/src/views/Researcher/GroupReport.less: -------------------------------------------------------------------------------- 1 | @import '../../assets/style.less'; 2 | 3 | #group-report-header { 4 | color: #colors[text-secondary]; 5 | font-size: 3em; 6 | font-weight: bolder; 7 | margin-left: 5%; 8 | margin-bottom: 3%; 9 | text-align: left; 10 | padding-top: 20px; 11 | } 12 | 13 | .cards { 14 | display: flex; 15 | justify-content: space-around; 16 | margin-bottom: 2rem; 17 | 18 | } 19 | 20 | #container-section { 21 | border : 2px solid #5BABDE; 22 | border-radius: 2px; 23 | } 24 | section { 25 | background-color: white; 26 | text-align: left; 27 | padding: 2rem; 28 | } 29 | 30 | #group-level-return { 31 | height: 55px; 32 | margin: 20px 50px; 33 | padding: 10px 20px; 34 | border-radius: 5px; 35 | font-size: 20px; 36 | font-weight: 800; 37 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; 38 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.377); 39 | } -------------------------------------------------------------------------------- /server/config/functions/bootstrap.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fixRoles = async () => { 4 | 5 | // get all the current roles 6 | const roles = await strapi.query('role', 'users-permissions').find({}, []) 7 | 8 | // if the authenticated role has the default name, update it 9 | const authenticated = roles.find(role => role.type === 'authenticated') 10 | if (authenticated.name !== 'Classroom Manager') await strapi.query('role', 'users-permissions').update({ id: authenticated.id }, { name: 'Classroom Manager' }) 11 | 12 | // if the student role doesn't exist, alert the user 13 | const student = roles.find(role => role.type === 'student') 14 | if (!student) console.log('There is currently not a student role! Make sure to make one.') 15 | } 16 | 17 | module.exports = async () => { 18 | 19 | // Connect to the compile queue 20 | strapi.services.submission.initCompileQueue() 21 | 22 | // Check the student roles 23 | await fixRoles() 24 | } 25 | -------------------------------------------------------------------------------- /server/api/session/models/session.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "sessions", 4 | "info": { 5 | "name": "Session", 6 | "description": "" 7 | }, 8 | "options": { 9 | "increments": true, 10 | "timestamps": true, 11 | "draftAndPublish": false 12 | }, 13 | "attributes": { 14 | "classroom": { 15 | "via": "sessions", 16 | "model": "classroom" 17 | }, 18 | "students": { 19 | "via": "sessions", 20 | "collection": "student" 21 | }, 22 | "submissions": { 23 | "via": "session", 24 | "collection": "submission" 25 | }, 26 | "saves": { 27 | "collection": "save", 28 | "via": "session" 29 | }, 30 | "unit": { 31 | "model": "unit" 32 | }, 33 | "grade": { 34 | "model": "grade" 35 | }, 36 | "learning_standard": { 37 | "model": "learning-standard" 38 | }, 39 | "arduino": { 40 | "type": "text" 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /client/src/views/Researcher/GroupReport.jsx: -------------------------------------------------------------------------------- 1 | import NavBar from '../../components/NavBar/NavBar'; 2 | import React from 'react'; 3 | import { useNavigate } from 'react-router-dom'; 4 | import './GroupReport.less'; 5 | import { confirmRedirect } from '../../App'; 6 | 7 | export default function GroupReport(props) { 8 | const navigate = useNavigate(); 9 | confirmRedirect(); 10 | return ( 11 |
12 | 13 | {/*

Group Report

*/} 14 |
15 |
Group Level Report
16 | 17 | {/* Do we need a menu button to go back to report landing page?*/} 18 | 26 |
27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /server/api/learning-standard/models/learning-standard.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "learning_standards", 4 | "info": { 5 | "name": "learning Standard", 6 | "description": "" 7 | }, 8 | "options": { 9 | "increments": true, 10 | "timestamps": true, 11 | "draftAndPublish": false 12 | }, 13 | "attributes": { 14 | "number": { 15 | "type": "decimal", 16 | "required": true 17 | }, 18 | "name": { 19 | "type": "string", 20 | "required": true 21 | }, 22 | "expectations": { 23 | "type": "text", 24 | "required": false 25 | }, 26 | "days": { 27 | "via": "learning_standard", 28 | "collection": "day" 29 | }, 30 | "unit": { 31 | "model": "unit", 32 | "via": "learning_standards" 33 | }, 34 | "teks": { 35 | "type": "string", 36 | "unique": false, 37 | "required": false 38 | }, 39 | "link": { 40 | "type": "string" 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /server/config/policies/hasClassroom.js: -------------------------------------------------------------------------------- 1 | // 2 | // Check if the current user belongs to this classroom 3 | // 4 | module.exports = async (ctx, next) => { 5 | if (ctx.state.user && ctx.state.user.role.name === 'Researcher') { 6 | return await next(); 7 | } 8 | // get the target classroom from either the 9 | // request body or the query params 10 | let { classroom } = ctx.request.body; 11 | if (!classroom) classroom = ctx.params.id; 12 | 13 | // make sure the classroom id 14 | // is in the proper format 15 | classroom = parseInt(classroom); 16 | 17 | // get the classrooms that the user belongs to 18 | const { id } = ctx.state.user; 19 | const { classrooms } = ( 20 | await strapi.services['classroom-manager'].findById(id) 21 | ).classroomManager; 22 | 23 | // check if the target classroom is one of the user's classrooms 24 | if (classrooms.length && classrooms.find((cr) => cr.id === classroom)) { 25 | return await next(); 26 | } 27 | 28 | ctx.unauthorized(`You're not allowed to perform this action!`); 29 | }; 30 | -------------------------------------------------------------------------------- /server/config/policies/hasStudentsClassroom.js: -------------------------------------------------------------------------------- 1 | // 2 | // Check if the current user manages a classroom that this student belongs to 3 | // 4 | module.exports = async (ctx, next) => { 5 | 6 | // get the target student from either the 7 | // request body or the query params 8 | let { student } = ctx.request.body 9 | if (!student) student = ctx.params.id 10 | 11 | // make sure the student id 12 | // is in the proper format 13 | student = parseInt(student) 14 | 15 | // get the classrooms that the user belongs to 16 | const { id } = ctx.state.user 17 | const classrooms = await strapi.services['classroom-manager'].findClassesById(id) 18 | 19 | 20 | // check if the target student is in one of the user's classrooms 21 | if (classrooms.length && classrooms.some(classroom => { 22 | return classroom.students.some(s => { 23 | return s.id === student 24 | }) 25 | })) { 26 | return await next() 27 | } 28 | 29 | ctx.unauthorized(`You're not allowed to perform this action!`) 30 | } -------------------------------------------------------------------------------- /server/api/learning-standard/controllers/learning-standard.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/controllers.html#core-controllers) 5 | * to customize this controller 6 | */ 7 | 8 | module.exports = { 9 | async update(ctx) { 10 | const { id } = ctx.params; 11 | 12 | // ensure request was not sent as formdata 13 | if (ctx.is('multipart')) 14 | return ctx.badRequest('Multipart requests are not accepted!', { 15 | id: 'Learning-standard.update.format.invalid', 16 | error: 'ValidationError', 17 | }); 18 | 19 | // validate the request 20 | const { name, expectations, teks } = ctx.request.body; 21 | if (!name) 22 | return ctx.badRequest('A name, teks and expectations must be provided!', { 23 | id: 'Learning-standard.update.body.invalid', 24 | error: 'ValidationError', 25 | }); 26 | 27 | return await strapi.services['learning-standard'].update( 28 | { id }, 29 | ctx.request.body 30 | ); 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /server/api/session/controllers/session.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/controllers.html#core-controllers) 5 | * to customize this controller 6 | */ 7 | const { sanitizeEntity } = require('strapi-utils/lib'); 8 | 9 | 10 | module.exports = { 11 | async arduinoUpdate(ctx) { 12 | const {id} = ctx.params; 13 | const {arduino} = ctx.request.body; 14 | const session = await strapi.services.session.findOne({ id }); 15 | //console.log(session) 16 | //console.log(arduino) 17 | session.arduino = arduino; 18 | const updatedSession = await strapi.services.session.update({id}, session); 19 | return updatedSession 20 | 21 | }, 22 | async findOne(ctx) { 23 | // Extract the ID from the request parameters 24 | const { id } = ctx.params; 25 | // Implement custom logic to fetch a single record by ID 26 | const session = await strapi.services.session.findOne({ id }); 27 | // Customize the response as needed 28 | ctx.send(session); 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /.github/workflows/deploy-production.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Production 2 | on: 3 | release: 4 | types: [created] 5 | branches: [master] 6 | jobs: 7 | run: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout the repo 11 | uses: actions/checkout@v2 12 | - name: Get the version 13 | id: get_version 14 | run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/} 15 | - name: Built, test, and deploy app 16 | uses: STEM-C/auto/build-test-deploy@v0.7.2 17 | with: 18 | image_tag: ${{ steps.get_version.outputs.VERSION }} 19 | app_name: casmm 20 | github_token: ${{ secrets.GITHUB_TOKEN }} 21 | env: 22 | HEROKU_API_KEY: ${{ secrets.HEROKU_TOKEN }} 23 | - name: Create Sentry release 24 | uses: getsentry/action-release@v1 25 | env: 26 | SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} 27 | SENTRY_ORG: engaging-learning-lab-uf 28 | SENTRY_PROJECT: casmm 29 | with: 30 | environment: production 31 | -------------------------------------------------------------------------------- /server/api/strapiusers/controllers/strapiusers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const crypto = require('crypto'); 4 | const _ = require('lodash'); 5 | const grant = require('grant-koa'); 6 | const { sanitizeEntity } = require('strapi-utils'); 7 | 8 | 9 | module.exports = { 10 | 11 | 12 | async findSuperAdmins() { 13 | try { 14 | // Find the role representing super admins 15 | const superAdminRole = await strapi 16 | .query('role', 'admin') 17 | .findOne({ code: 'strapi-super-admin' }); 18 | 19 | if (!superAdminRole) { 20 | console.error('Super admin role not found'); 21 | return []; 22 | } 23 | //console.log('Role name: ', superAdminRole); 24 | // Find users assigned the super admin role 25 | const superAdmins = await strapi 26 | .query('user', 'admin',) 27 | .find(); 28 | 29 | return superAdmins; 30 | } catch (error) { 31 | console.error('Error finding super admins:', error); 32 | return []; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /compile/src/controllers/job.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const arduino_dir = path.resolve(__dirname, '../arduino-1.8.5'); 3 | const arduino = new (require('../models/arduino.js'))(arduino_dir); 4 | const { compileLog } = require('../utils/base'); 5 | 6 | module.exports.processJob = async (job) => { 7 | // get the job data 8 | const { sketch, board, submissionId } = job.data; 9 | 10 | // create an identified 11 | const identifier = `job ${job.id}, submission ${submissionId}`; 12 | 13 | // update the job progress 14 | await job.progress(50); 15 | compileLog(`Compiling ${identifier}`); 16 | 17 | let result = {}; 18 | try { 19 | result = await arduino.compile(sketch, board); 20 | } catch (err) { 21 | // set the result object to failed 22 | result.success = false; 23 | result.stderr = err.message; 24 | 25 | compileLog(`Failed to compile ${identifier} - ${err}`); 26 | } 27 | 28 | // update the job progress 29 | await job.progress(100); 30 | compileLog(`Completed ${identifier}`); 31 | 32 | // return the compiled result 33 | return result; 34 | }; 35 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | server: 5 | container_name: casmm-server-dev 6 | image: strapi/strapi 7 | restart: always 8 | environment: 9 | - DATABASE_HOST=db 10 | ports: 11 | - 1337:1337 12 | volumes: 13 | - ./server:/srv/app 14 | depends_on: 15 | - db 16 | - compile_queue 17 | compile: 18 | container_name: casmm-compile-dev 19 | build: ./compile 20 | restart: always 21 | depends_on: 22 | - compile_queue 23 | db: 24 | container_name: casmm-db-dev 25 | image: postgres 26 | restart: always 27 | ports: 28 | - 5432:5432 29 | volumes: 30 | - /var/lib/postgresql/data 31 | - ./scripts:/docker-entrypoint-initdb.d 32 | environment: 33 | POSTGRES_DB: strapi 34 | POSTGRES_USER: postgres 35 | POSTGRES_PASSWORD: postgres 36 | ENVIRONMENT: development 37 | DATABASE_URL: strapi 38 | SCRIPT_PATH: /docker-entrypoint-initdb.d 39 | compile_queue: 40 | container_name: casmm-compile_queue-dev 41 | image: redis 42 | restart: always -------------------------------------------------------------------------------- /server/config/middleware.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | timeout: 100, 3 | load: { 4 | before: ["responseTime", "logger", "cors", "responses", "gzip"], 5 | order: ["proxy", "parser"], 6 | after: ["router"] 7 | }, 8 | settings: { 9 | public: { 10 | path: './public', 11 | maxAge: 60000, 12 | }, 13 | proxy: { 14 | enabled: true, 15 | clientPath: './public/client', 16 | }, 17 | parser: { 18 | jsonLimit: '10mb' 19 | }, 20 | gzip: { 21 | enabled: true, 22 | options: { 23 | br: false 24 | } 25 | }, 26 | // logger: { 27 | // // dev + prod 28 | // level: debug + info, 29 | // requests: true + false 30 | // } 31 | 32 | // dev 33 | cors: { 34 | enabled: true, 35 | origin: [ 'https://www.casmm.org', 'http://localhost:1337', 'http://localhost:3000'], 36 | headers: '*', 37 | }, 38 | }, 39 | } -------------------------------------------------------------------------------- /docker-compose.test.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | server: 5 | container_name: casmm-server-test 6 | image: strapi/strapi 7 | restart: always 8 | environment: 9 | - DATABASE_HOST=db 10 | ports: 11 | - 1337:1337 12 | volumes: 13 | - ./server:/srv/app 14 | depends_on: 15 | - db 16 | - compile_queue 17 | compile: 18 | container_name: casmm-compile-test 19 | build: ./compile 20 | restart: always 21 | depends_on: 22 | - compile_queue 23 | db: 24 | container_name: casmm-db-test 25 | image: postgres 26 | restart: always 27 | ports: 28 | - 5432:5432 29 | volumes: 30 | - /var/lib/postgresql/data 31 | - ./scripts:/docker-entrypoint-initdb.d 32 | environment: 33 | POSTGRES_DB: strapi 34 | POSTGRES_USER: postgres 35 | POSTGRES_PASSWORD: postgres 36 | ENVIRONMENT: test 37 | DATABASE_URL: strapi 38 | SCRIPT_PATH: /docker-entrypoint-initdb.d 39 | compile_queue: 40 | container_name: casmm-compile_queue-test 41 | image: redis 42 | restart: always -------------------------------------------------------------------------------- /client/src/views/Student/form.less: -------------------------------------------------------------------------------- 1 | @import "../../assets/style.less"; 2 | 3 | .container { 4 | background-color: #colors[primary]; 5 | height: 100%; 6 | min-height: 100vh; 7 | width: 100%; 8 | min-width: 100vw; 9 | text-align: center; 10 | } 11 | 12 | #about-content-container { 13 | margin: 8vh auto 5vh auto; 14 | padding: 5vh 5vw; 15 | width: 80vw; 16 | background: #colors[tertiary]; 17 | border-radius: 20px; 18 | border: 2px solid #colors[secondary]; 19 | 20 | #logos { 21 | width: 50vw; 22 | margin: auto; 23 | 24 | img { 25 | width: 20%; 26 | height: auto; 27 | margin: auto; 28 | } 29 | } 30 | 31 | #title { 32 | font-size: 3em; 33 | color: #dc3545; 34 | margin-bottom: 0; 35 | } 36 | 37 | #secondary-title { 38 | font-size: 2em; 39 | color: #colors[text-primary]; 40 | } 41 | 42 | #divider { 43 | width: 95%; 44 | height: 0; 45 | margin: 5vh 0; 46 | border-bottom: 1px solid #colors[secondary]; 47 | opacity: 20%; 48 | } 49 | 50 | p { 51 | font-size: 1.5em; 52 | color: #colors[text-primary]; 53 | } 54 | } -------------------------------------------------------------------------------- /client/src/Utils/userState.js: -------------------------------------------------------------------------------- 1 | import { createGlobalState } from 'react-hooks-global-state'; 2 | 3 | // Get the role of user from the session storage 4 | export const getCurrUser = () => { 5 | const result = JSON.parse(sessionStorage.getItem('user')); 6 | if (!result) { 7 | return { 8 | role: 'DefaultUser', 9 | }; 10 | } 11 | if (!result.role) { 12 | return { 13 | role: 'Student', 14 | }; 15 | } else if (result.role.type === 'content_creator') { 16 | return { 17 | role: 'ContentCreator', 18 | name: result.role.name, 19 | }; 20 | } else if (result.role.type === 'researcher') { 21 | return { 22 | role: 'Researcher', 23 | name: result.role.name, 24 | }; 25 | } else if (result.role.type === 'authenticated') { 26 | return { 27 | role: 'Mentor', 28 | name: result.role.name, 29 | }; 30 | } 31 | }; 32 | 33 | const { setGlobalState, useGlobalState } = createGlobalState({ 34 | currUser: getCurrUser(), 35 | }); 36 | 37 | export const setUserState = (s) => { 38 | setGlobalState('currUser', s); 39 | }; 40 | 41 | export { useGlobalState }; 42 | -------------------------------------------------------------------------------- /client/src/views/TeacherLogin/Sorry.less: -------------------------------------------------------------------------------- 1 | @import "../../assets/style.less"; 2 | 3 | .container { 4 | background-color: #colors[primary]; 5 | height: 100%; 6 | min-height: 100vh; 7 | width: 100%; 8 | min-width: 100vw; 9 | text-align: center; 10 | } 11 | 12 | #about-content-container { 13 | margin: 8vh auto 5vh auto; 14 | padding: 5vh 5vw; 15 | width: 80vw; 16 | background: #colors[tertiary]; 17 | border-radius: 20px; 18 | border: 2px solid #colors[secondary]; 19 | 20 | #logos { 21 | width: 50vw; 22 | margin: auto; 23 | 24 | img { 25 | width: 20%; 26 | height: auto; 27 | margin: auto; 28 | } 29 | } 30 | 31 | #title { 32 | font-size: 3em; 33 | color: #dc3545; 34 | margin-bottom: 0; 35 | } 36 | 37 | #secondary-title { 38 | font-size: 2em; 39 | color: #colors[text-primary]; 40 | } 41 | 42 | #divider { 43 | width: 95%; 44 | height: 0; 45 | margin: 5vh 0; 46 | border-bottom: 1px solid #colors[secondary]; 47 | opacity: 20%; 48 | } 49 | 50 | p { 51 | font-size: 1.5em; 52 | color: #colors[text-primary]; 53 | } 54 | } -------------------------------------------------------------------------------- /client/src/views/About/About.less: -------------------------------------------------------------------------------- 1 | @import "../../assets/style.less"; 2 | 3 | .container { 4 | background-color: #colors[primary]; 5 | height: 100%; 6 | min-height: 100vh; 7 | width: 100%; 8 | min-width: 100vw; 9 | text-align: center; 10 | } 11 | 12 | #about-content-container { 13 | margin: 8vh auto 5vh auto; 14 | padding: 5vh 5vw; 15 | width: 80vw; 16 | background: #colors[tertiary]; 17 | border-radius: 20px; 18 | border: 2px solid #colors[secondary]; 19 | 20 | #logos { 21 | width: 50vw; 22 | margin: auto; 23 | 24 | img { 25 | width: 20%; 26 | height: auto; 27 | margin: auto; 28 | } 29 | } 30 | 31 | #title { 32 | font-size: 3em; 33 | color: #colors[text-primary]; 34 | margin-bottom: 0; 35 | } 36 | 37 | #secondary-title { 38 | font-size: 2em; 39 | color: #colors[text-primary]; 40 | } 41 | 42 | #divider { 43 | width: 95%; 44 | height: 0; 45 | margin: 5vh 0; 46 | border-bottom: 1px solid #colors[secondary]; 47 | opacity: 20%; 48 | } 49 | 50 | p { 51 | font-size: 1.5em; 52 | color: #colors[text-primary]; 53 | } 54 | } -------------------------------------------------------------------------------- /server/api/unit/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/units", 6 | "handler": "unit.find", 7 | "config": { 8 | "policies": [] 9 | } 10 | }, 11 | { 12 | "method": "GET", 13 | "path": "/units/count", 14 | "handler": "unit.count", 15 | "config": { 16 | "policies": [] 17 | } 18 | }, 19 | { 20 | "method": "GET", 21 | "path": "/units/:id", 22 | "handler": "unit.findOne", 23 | "config": { 24 | "policies": [] 25 | } 26 | }, 27 | { 28 | "method": "POST", 29 | "path": "/units", 30 | "handler": "unit.create", 31 | "config": { 32 | "policies": [] 33 | } 34 | }, 35 | { 36 | "method": "PUT", 37 | "path": "/units/:id", 38 | "handler": "unit.update", 39 | "config": { 40 | "policies": [] 41 | } 42 | }, 43 | { 44 | "method": "DELETE", 45 | "path": "/units/:id", 46 | "handler": "unit.delete", 47 | "config": { 48 | "policies": [] 49 | } 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /server/api/block/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/blocks", 6 | "handler": "block.find", 7 | "config": { 8 | "policies": [] 9 | } 10 | }, 11 | { 12 | "method": "GET", 13 | "path": "/blocks/count", 14 | "handler": "block.count", 15 | "config": { 16 | "policies": [] 17 | } 18 | }, 19 | { 20 | "method": "GET", 21 | "path": "/blocks/:id", 22 | "handler": "block.findOne", 23 | "config": { 24 | "policies": [] 25 | } 26 | }, 27 | { 28 | "method": "POST", 29 | "path": "/blocks", 30 | "handler": "block.create", 31 | "config": { 32 | "policies": [] 33 | } 34 | }, 35 | { 36 | "method": "PUT", 37 | "path": "/blocks/:id", 38 | "handler": "block.update", 39 | "config": { 40 | "policies": [] 41 | } 42 | }, 43 | { 44 | "method": "DELETE", 45 | "path": "/blocks/:id", 46 | "handler": "block.delete", 47 | "config": { 48 | "policies": [] 49 | } 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /server/api/grade/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/grades", 6 | "handler": "grade.find", 7 | "config": { 8 | "policies": [] 9 | } 10 | }, 11 | { 12 | "method": "GET", 13 | "path": "/grades/count", 14 | "handler": "grade.count", 15 | "config": { 16 | "policies": [] 17 | } 18 | }, 19 | { 20 | "method": "GET", 21 | "path": "/grades/:id", 22 | "handler": "grade.findOne", 23 | "config": { 24 | "policies": [] 25 | } 26 | }, 27 | { 28 | "method": "POST", 29 | "path": "/grades", 30 | "handler": "grade.create", 31 | "config": { 32 | "policies": [] 33 | } 34 | }, 35 | { 36 | "method": "PUT", 37 | "path": "/grades/:id", 38 | "handler": "grade.update", 39 | "config": { 40 | "policies": [] 41 | } 42 | }, 43 | { 44 | "method": "DELETE", 45 | "path": "/grades/:id", 46 | "handler": "grade.delete", 47 | "config": { 48 | "policies": [] 49 | } 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /server/api/school/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/schools", 6 | "handler": "school.find", 7 | "config": { 8 | "policies": [] 9 | } 10 | }, 11 | { 12 | "method": "GET", 13 | "path": "/schools/count", 14 | "handler": "school.count", 15 | "config": { 16 | "policies": [] 17 | } 18 | }, 19 | { 20 | "method": "GET", 21 | "path": "/schools/:id", 22 | "handler": "school.findOne", 23 | "config": { 24 | "policies": [] 25 | } 26 | }, 27 | { 28 | "method": "POST", 29 | "path": "/schools", 30 | "handler": "school.create", 31 | "config": { 32 | "policies": [] 33 | } 34 | }, 35 | { 36 | "method": "PUT", 37 | "path": "/schools/:id", 38 | "handler": "school.update", 39 | "config": { 40 | "policies": [] 41 | } 42 | }, 43 | { 44 | "method": "DELETE", 45 | "path": "/schools/:id", 46 | "handler": "school.delete", 47 | "config": { 48 | "policies": [] 49 | } 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /server/api/mentor/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/mentors", 6 | "handler": "mentor.find", 7 | "config": { 8 | "policies": [] 9 | } 10 | }, 11 | { 12 | "method": "GET", 13 | "path": "/mentors/count", 14 | "handler": "mentor.count", 15 | "config": { 16 | "policies": [] 17 | } 18 | }, 19 | { 20 | "method": "GET", 21 | "path": "/mentors/:id", 22 | "handler": "mentor.findOne", 23 | "config": { 24 | "policies": [] 25 | } 26 | }, 27 | { 28 | "method": "POST", 29 | "path": "/mentors", 30 | "handler": "mentor.create", 31 | "config": { 32 | "policies": ["global::isClassroomManager"] 33 | } 34 | }, 35 | { 36 | "method": "PUT", 37 | "path": "/mentors/:id", 38 | "handler": "mentor.update", 39 | "config": { 40 | "policies": [] 41 | } 42 | }, 43 | { 44 | "method": "DELETE", 45 | "path": "/mentors/:id", 46 | "handler": "mentor.delete", 47 | "config": { 48 | "policies": [] 49 | } 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /client/src/views/Mentor/Classroom/Home/DisplayCodeModal.jsx: -------------------------------------------------------------------------------- 1 | import {Modal, Button} from 'antd'; 2 | import React, {useState} from "react"; 3 | import './Home.less' 4 | 5 | export default function DisplayCodeModal(props) { 6 | const [visible, setVisible] = useState(false); 7 | const {code} = props; 8 | 9 | const showModal = () => { 10 | setVisible(true) 11 | }; 12 | 13 | const handleCancel = () => { 14 | setVisible(false) 15 | }; 16 | 17 | const handleOk = () => { 18 | setVisible(false) 19 | }; 20 | 21 | return ( 22 |
23 | 24 | 31 | OK 32 | , 33 | ]} 34 | > 35 |
{code}
36 |
37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /server/api/student/models/student.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "students", 4 | "info": { 5 | "name": "Student", 6 | "description": "" 7 | }, 8 | "options": { 9 | "increments": true, 10 | "timestamps": true, 11 | "draftAndPublish": false 12 | }, 13 | "attributes": { 14 | "name": { 15 | "type": "string", 16 | "regex": "^([A-Za-z]+)\\s*([A-Za-z]*)\\s+([A-Za-z])\\.$" 17 | }, 18 | "character": { 19 | "type": "string" 20 | }, 21 | "classroom": { 22 | "via": "students", 23 | "model": "classroom" 24 | }, 25 | "sessions": { 26 | "via": "students", 27 | "collection": "session", 28 | "dominant": true 29 | }, 30 | "enrolled": { 31 | "type": "boolean", 32 | "default": true, 33 | "required": true 34 | }, 35 | "last_logged_in": { 36 | "type": "datetime" 37 | }, 38 | "recordings": { 39 | "collection": "file", 40 | "via": "related", 41 | "allowedTypes": [ 42 | "images", 43 | "files", 44 | "videos" 45 | ], 46 | "plugin": "upload", 47 | "required": false, 48 | "pluginOptions": {} 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /server/api/submission/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/submissions", 6 | "handler": "submission.find", 7 | "config": { 8 | "policies": [] 9 | } 10 | }, 11 | { 12 | "method": "GET", 13 | "path": "/submissions/count", 14 | "handler": "submission.count", 15 | "config": { 16 | "policies": [] 17 | } 18 | }, 19 | { 20 | "method": "GET", 21 | "path": "/submissions/:id", 22 | "handler": "submission.findOne", 23 | "config": { 24 | "policies": ["global::isStudent"] 25 | } 26 | }, 27 | { 28 | "method": "POST", 29 | "path": "/submissions", 30 | "handler": "submission.create", 31 | "config": { 32 | "policies": ["global::isStudent"] 33 | } 34 | }, 35 | { 36 | "method": "PUT", 37 | "path": "/submissions/:id", 38 | "handler": "submission.update", 39 | "config": { 40 | "policies": [] 41 | } 42 | }, 43 | { 44 | "method": "DELETE", 45 | "path": "/submissions/:id", 46 | "handler": "submission.delete", 47 | "config": { 48 | "policies": [] 49 | } 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /server/api/selection/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/selections", 6 | "handler": "selection.find", 7 | "config": { 8 | "policies": [] 9 | } 10 | }, 11 | { 12 | "method": "GET", 13 | "path": "/selections/count", 14 | "handler": "selection.count", 15 | "config": { 16 | "policies": [] 17 | } 18 | }, 19 | { 20 | "method": "GET", 21 | "path": "/selections/:id", 22 | "handler": "selection.findOne", 23 | "config": { 24 | "policies": [] 25 | } 26 | }, 27 | { 28 | "method": "POST", 29 | "path": "/selections", 30 | "handler": "selection.create", 31 | "config": { 32 | "policies": ["global::isClassroomManager", "global::hasClassroom"] 33 | } 34 | }, 35 | { 36 | "method": "PUT", 37 | "path": "/selections/:id", 38 | "handler": "selection.update", 39 | "config": { 40 | "policies": [] 41 | } 42 | }, 43 | { 44 | "method": "DELETE", 45 | "path": "/selections/:id", 46 | "handler": "selection.delete", 47 | "config": { 48 | "policies": [] 49 | } 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /server/api/classroom/models/classroom.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "classrooms", 4 | "info": { 5 | "name": "Classroom", 6 | "description": "" 7 | }, 8 | "options": { 9 | "increments": true, 10 | "timestamps": true, 11 | "draftAndPublish": false 12 | }, 13 | "attributes": { 14 | "name": { 15 | "type": "string" 16 | }, 17 | "school": { 18 | "model": "school", 19 | "via": "classrooms" 20 | }, 21 | "sessions": { 22 | "via": "classroom", 23 | "collection": "session" 24 | }, 25 | "mentors": { 26 | "collection": "mentor", 27 | "via": "classrooms" 28 | }, 29 | "students": { 30 | "via": "classroom", 31 | "collection": "student" 32 | }, 33 | "code": { 34 | "type": "string", 35 | "required": true 36 | }, 37 | "grade": { 38 | "via": "classrooms", 39 | "model": "grade" 40 | }, 41 | "selections": { 42 | "via": "classroom", 43 | "collection": "selection" 44 | }, 45 | "cc_workspaces": { 46 | "private": true, 47 | "via": "classroom", 48 | "collection": "cc-workspace" 49 | }, 50 | "form": { 51 | "type": "string" 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /server/api/submission/models/submission.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "submissions", 4 | "info": { 5 | "name": "Submission" 6 | }, 7 | "options": { 8 | "increments": true, 9 | "timestamps": true 10 | }, 11 | "attributes": { 12 | "status": { 13 | "type": "string", 14 | "default": "WAITING", 15 | "required": true 16 | }, 17 | "workspace": { 18 | "type": "text", 19 | "required": true 20 | }, 21 | "success": { 22 | "type": "boolean", 23 | "required": false 24 | }, 25 | "hex": { 26 | "type": "text" 27 | }, 28 | "stdout": { 29 | "type": "text" 30 | }, 31 | "stderr": { 32 | "type": "text" 33 | }, 34 | "board": { 35 | "type": "string", 36 | "required": true 37 | }, 38 | "sketch": { 39 | "type": "text", 40 | "required": true 41 | }, 42 | "sandbox": { 43 | "type": "boolean", 44 | "default": false, 45 | "required": true 46 | }, 47 | "session": { 48 | "via": "submissions", 49 | "model": "session" 50 | }, 51 | "day": { 52 | "model": "day" 53 | }, 54 | "job_id": { 55 | "type": "biginteger" 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /client/src/assets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | h3, p { 6 | margin: 0; 7 | } 8 | 9 | .flex { 10 | display: flex; 11 | } 12 | 13 | .flex-column { 14 | flex-direction: column; 15 | } 16 | 17 | .space-between { 18 | justify-content: space-between; 19 | } 20 | 21 | #container { 22 | height: 100vh; 23 | background-color: rgb(241, 241, 241); 24 | } 25 | 26 | .vertical-container { 27 | margin: 1.5em; 28 | } 29 | 30 | #nav-container { 31 | margin: 0; 32 | background-color: rgb(32, 145, 173); 33 | color: white; 34 | align-items: center; 35 | } 36 | 37 | #title { 38 | flex: 10; 39 | margin: 0.5em; 40 | } 41 | 42 | #action-btn-container { 43 | flex: 1; 44 | margin: 1em; 45 | } 46 | 47 | #top-container { 48 | flex: 1; 49 | margin-bottom: 0.75em 50 | } 51 | 52 | .card { 53 | background-color: white; 54 | padding: 1em; 55 | } 56 | 57 | #description-container { 58 | height: 100%; 59 | } 60 | 61 | #bottom-container { 62 | flex: 6; 63 | margin-top: 0.75em 64 | } 65 | 66 | #blockly-canvas { 67 | flex: 2; 68 | } 69 | 70 | #models-container { 71 | flex: 1; 72 | margin-left: 1.5em; 73 | } 74 | 75 | #mode-img { 76 | width: auto; 77 | height: auto; 78 | } 79 | 80 | -------------------------------------------------------------------------------- /server/admin/src/themes/colors.js: -------------------------------------------------------------------------------- 1 | const colors = { 2 | black: '#333740', 3 | white: '#ffffff', 4 | red: '#ff203c', 5 | orange: '#ff5d00', 6 | lightOrange: '#f64d0a', 7 | yellow: '#ffd500', 8 | green: '#6dbb1a', 9 | blue: '#0097f7', 10 | teal: '#5bc0de', 11 | pink: '#ff5b77', 12 | purple: '#613d7c', 13 | gray: '#464a4c', 14 | border: '#E3E9F3', 15 | 'gray-dark': '#292b2c', 16 | grayLight: '#636c72', 17 | 'gray-lighter': '#eceeef', 18 | 'gray-lightest': '#f7f7f9', 19 | brightGrey: '#f0f3f8', 20 | darkGrey: '#e3e9f3', 21 | lightGrey: '#fafafa', 22 | lightestGrey: '#fbfbfb', 23 | mediumGrey: '#F2F3F4', 24 | grey: '#9ea7b8', 25 | greyDark: '#292b2c', 26 | greyAlpha: 'rgba(227, 233, 243, 0.5)', 27 | lightBlue: '#E6F0FB', 28 | mediumBlue: '#007EFF', 29 | darkBlue: '#AED4FB', 30 | content: { 31 | background: '#fafafb', 32 | 'background-alpha': 'rgba(14, 22, 34, 0.02)', 33 | }, 34 | leftMenu: { 35 | 'link-hover': '#4e76a6', 36 | 'link-color': '#85bcde', 37 | 'title-color': '#5BABDE', 38 | }, 39 | strapi: { 40 | 'gray-light': '#eff3f6', 41 | gray: '#535f76', 42 | 'blue-darker': '#3D5C82', 43 | 'blue-dark': '#4e76a6', 44 | blue: '#5BABDE', 45 | }, 46 | }; 47 | 48 | export default colors; 49 | -------------------------------------------------------------------------------- /client/src/components/MentorSubHeader/MentorSubHeader.less: -------------------------------------------------------------------------------- 1 | @import '../../assets/style.less'; 2 | 3 | #page-header { 4 | width: 80vw; 5 | margin: 0 auto 0 auto; 6 | padding-bottom: 2vh; 7 | 8 | h1 { 9 | margin-bottom: 0; 10 | float: left; 11 | position: relative; 12 | display: flex; 13 | align-items: center; 14 | justify-content: center; 15 | color: #colors[text-secondary]; 16 | height: auto; 17 | padding: 10px 5px; 18 | width: 34%; 19 | border-radius: 80px; 20 | background: #colors[secondary]; 21 | font-size: 1.2em; 22 | top: 5vh; 23 | left: -2vw; 24 | z-index: 1; 25 | } 26 | 27 | #header-nav { 28 | position: relative; 29 | bottom: -2vh; 30 | float: right; 31 | margin-right: 0; 32 | white-space: nowrap; 33 | 34 | #link { 35 | //float: right; 36 | background: none !important; 37 | border: none; 38 | padding: 0 !important; 39 | display: inline-block; 40 | margin-left: 10px; 41 | 42 | &:focus { 43 | outline: none; 44 | } 45 | 46 | i { 47 | font-size: 2em; 48 | color: #colors[text-secondary]; 49 | 50 | &:hover { 51 | color: #colors[secondary]; 52 | } 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /server/api/blocks-category/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/blocks-categories", 6 | "handler": "blocks-category.find", 7 | "config": { 8 | "policies": [] 9 | } 10 | }, 11 | { 12 | "method": "GET", 13 | "path": "/blocks-categories/count", 14 | "handler": "blocks-category.count", 15 | "config": { 16 | "policies": [] 17 | } 18 | }, 19 | { 20 | "method": "GET", 21 | "path": "/blocks-categories/:id", 22 | "handler": "blocks-category.findOne", 23 | "config": { 24 | "policies": [] 25 | } 26 | }, 27 | { 28 | "method": "POST", 29 | "path": "/blocks-categories", 30 | "handler": "blocks-category.create", 31 | "config": { 32 | "policies": [] 33 | } 34 | }, 35 | { 36 | "method": "PUT", 37 | "path": "/blocks-categories/:id", 38 | "handler": "blocks-category.update", 39 | "config": { 40 | "policies": [] 41 | } 42 | }, 43 | { 44 | "method": "DELETE", 45 | "path": "/blocks-categories/:id", 46 | "handler": "blocks-category.delete", 47 | "config": { 48 | "policies": [] 49 | } 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /server/api/learning-standard/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/learning-standards", 6 | "handler": "learning-standard.find", 7 | "config": { 8 | "policies": [] 9 | } 10 | }, 11 | { 12 | "method": "GET", 13 | "path": "/learning-standards/count", 14 | "handler": "learning-standard.count", 15 | "config": { 16 | "policies": [] 17 | } 18 | }, 19 | { 20 | "method": "GET", 21 | "path": "/learning-standards/:id", 22 | "handler": "learning-standard.findOne", 23 | "config": { 24 | "policies": [] 25 | } 26 | }, 27 | { 28 | "method": "POST", 29 | "path": "/learning-standards", 30 | "handler": "learning-standard.create", 31 | "config": { 32 | "policies": [] 33 | } 34 | }, 35 | { 36 | "method": "PUT", 37 | "path": "/learning-standards/:id", 38 | "handler": "learning-standard.update", 39 | "config": { 40 | "policies": [] 41 | } 42 | }, 43 | { 44 | "method": "DELETE", 45 | "path": "/learning-standards/:id", 46 | "handler": "learning-standard.delete", 47 | "config": { 48 | "policies": [] 49 | } 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /client/src/views/Student/form.jsx: -------------------------------------------------------------------------------- 1 | import {React, useEffect, useState} from "react" 2 | import NavBar from "../../components/NavBar/NavBar" 3 | import "./form.less" 4 | import { getSupers } from "../../Utils/AuthRequests" 5 | import { sendEmailConfirmationEmail, getStudentClassroom } from "../../Utils/requests" 6 | 7 | 8 | export default function Form(props) { 9 | const superEmails = [] 10 | const[formcode, setFormCode] = useState('') 11 | 12 | 13 | useEffect ( () => { 14 | 15 | getStudentClassroom() 16 | .then((response) => { 17 | //console.log(response.data.classroom.form) 18 | setFormCode(`${response.data.classroom.form}?embedded=true`) 19 | 20 | //console.log(`${response.data.classroom.form}`) 21 | }) 22 | 23 | }, []) 24 | 25 | 26 | 27 | return ( 28 |
29 | 30 |
31 | 43 | 44 |
45 |
46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /server/admin/src/components/LeftMenu/LeftMenuHeader/Wrapper.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import Logo from '../../../assets/images/logo-strapi.png'; 5 | 6 | const Wrapper = styled.div` 7 | background-color: #007eff; 8 | padding-left: 2rem; 9 | height: ${(props) => props.theme.main.sizes.leftMenu.height}; 10 | 11 | .leftMenuHeaderLink { 12 | &:hover { 13 | text-decoration: none; 14 | } 15 | } 16 | 17 | .projectName { 18 | display: block; 19 | width: 100%; 20 | height: ${(props) => props.theme.main.sizes.leftMenu.height}; 21 | font-size: 2rem; 22 | letter-spacing: 0.2rem; 23 | color: $white; 24 | 25 | background-image: url(${Logo}); 26 | background-repeat: no-repeat; 27 | background-position: left center; 28 | background-size: auto 6.5rem; 29 | } 30 | `; 31 | //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 32 | //Change background-size to: auto 6.5rem 33 | 34 | Wrapper.defaultProps = { 35 | theme: { 36 | main: { 37 | colors: { 38 | leftMenu: {}, 39 | }, 40 | sizes: { 41 | header: {}, 42 | leftMenu: {}, 43 | }, 44 | }, 45 | }, 46 | }; 47 | 48 | Wrapper.propTypes = { 49 | theme: PropTypes.object, 50 | }; 51 | 52 | export default Wrapper; 53 | -------------------------------------------------------------------------------- /server/api/learning-components/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/learning-components", 6 | "handler": "learning-components.find", 7 | "config": { 8 | "policies": [] 9 | } 10 | }, 11 | { 12 | "method": "GET", 13 | "path": "/learning-components/count", 14 | "handler": "learning-components.count", 15 | "config": { 16 | "policies": [] 17 | } 18 | }, 19 | { 20 | "method": "GET", 21 | "path": "/learning-components/:id", 22 | "handler": "learning-components.findOne", 23 | "config": { 24 | "policies": [] 25 | } 26 | }, 27 | { 28 | "method": "POST", 29 | "path": "/learning-components", 30 | "handler": "learning-components.create", 31 | "config": { 32 | "policies": [] 33 | } 34 | }, 35 | { 36 | "method": "PUT", 37 | "path": "/learning-components/:id", 38 | "handler": "learning-components.update", 39 | "config": { 40 | "policies": [] 41 | } 42 | }, 43 | { 44 | "method": "DELETE", 45 | "path": "/learning-components/:id", 46 | "handler": "learning-components.delete", 47 | "config": { 48 | "policies": [] 49 | } 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /client/src/components/NavBar/NavBar.less: -------------------------------------------------------------------------------- 1 | @import "../../assets/style.less"; 2 | 3 | #navBar { 4 | display: flex; 5 | justify-content: space-between; 6 | align-items: center; 7 | flex-wrap: nowrap; 8 | width: 100%; 9 | position: -webkit-sticky; 10 | position: fixed; 11 | top: 0; 12 | z-index: 2; 13 | height: 8vh; 14 | background-color: #colors[primary]; 15 | padding-right: 2vw; 16 | 17 | -moz-box-shadow: 0 1px 5px -1px darken(#colors[primary], 90%); 18 | -webkit-box-shadow: 0 1px 5px -1px darken(#colors[primary], 90%); 19 | box-shadow: 0 1px 5px -1px darken(#colors[primary], 90%); 20 | 21 | #link { 22 | #casmm-logo { 23 | position: relative; 24 | bottom: 0; 25 | height: 6vh; 26 | width: auto; 27 | } 28 | } 29 | } 30 | 31 | #menu-link { 32 | background: none !important; 33 | border: none; 34 | padding: 0 !important; 35 | display: inline-block; 36 | 37 | &:focus { 38 | outline: none; 39 | } 40 | 41 | &:hover { 42 | cursor: pointer; 43 | } 44 | } 45 | 46 | .ant-dropdown-link { 47 | color: #colors[tertiary]; 48 | background: none !important; 49 | border: none; 50 | padding: 0 !important; 51 | 52 | &:focus { 53 | outline: none; 54 | } 55 | 56 | &:hover { 57 | color: #colors[secondary]; 58 | cursor: pointer; 59 | } 60 | } -------------------------------------------------------------------------------- /client/src/components/DayPanels/BlocklyCanvasPanel/BlocklyCanvasPanel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PublicCanvas from './canvas/PublicCanvas'; 3 | import StudentCanvas from './canvas/StudentCanvas'; 4 | import MentorCanvas from './canvas/MentorCanvas'; 5 | import ContentCreatorCanvas from './canvas/ContentCreatorCanvas'; 6 | import { useGlobalState } from '../../../Utils/userState'; 7 | 8 | const BlocklyCanvasPanel = ({ day, isSandbox, setDay }) => { 9 | const [value] = useGlobalState('currUser'); 10 | 11 | const userRole = value.role; 12 | 13 | switch (userRole) { 14 | case 'DefaultUser': 15 | return ; 16 | case 'Student': 17 | return ; 18 | case 'Mentor': 19 | return ; 25 | case 'ContentCreator': 26 | return ( 27 | 33 | ); 34 | default: 35 | return
; 36 | } 37 | }; 38 | 39 | export default BlocklyCanvasPanel; 40 | -------------------------------------------------------------------------------- /client/src/views/Mentor/Dashboard/DashboardDisplayCodeModal.jsx: -------------------------------------------------------------------------------- 1 | import {Modal, Button} from 'antd'; 2 | import React, {useState} from "react"; 3 | import './Dashboard.less' 4 | 5 | export default function DashboardDisplayCodeModal(props) { 6 | const [visible, setVisible] = useState(false); 7 | const {code} = props; 8 | 9 | const showModal = () => { 10 | setVisible(true) 11 | }; 12 | 13 | const handleCancel = () => { 14 | setVisible(false) 15 | }; 16 | 17 | const handleOk = () => { 18 | setVisible(false) 19 | }; 20 | 21 | return ( 22 |
23 | 27 | 34 | OK 35 | , 36 | ]} 37 | > 38 |
{code}
39 |
40 |
41 | ); 42 | } -------------------------------------------------------------------------------- /server/api/cc-workspace/config/policies/isContentCreatorOrHasClassroom.js: -------------------------------------------------------------------------------- 1 | // 2 | // Check if the current user belongs to this classroom 3 | // 4 | module.exports = async (ctx, next) => { 5 | if (ctx.state.user && ctx.state.user.role.name === 'Content Creator') { 6 | // Go to next policy or controller 7 | return await next(); 8 | } 9 | let classroomId; 10 | 11 | // get the target classroom from either the 12 | // request body or the query params 13 | let { id } = ctx.params; 14 | if (id) { 15 | id = parseInt(id); 16 | const workspace = await strapi.services['cc-workspace'].findOne( 17 | { id: id }, 18 | ['classroom.id'] 19 | ); 20 | if (workspace && workspace.classroom) { 21 | classroomId = workspace.classroom.id; 22 | } 23 | } else { 24 | classroomId = ctx.request.body.classroomId; 25 | classroomId = parseInt(classroomId); 26 | } 27 | 28 | const { id: userId } = ctx.state.user; 29 | const { classrooms } = ( 30 | await strapi.services['classroom-manager'].findById(userId) 31 | ).classroomManager; 32 | 33 | //check if the target classroom is one of the user's classrooms 34 | if (classrooms.length && classrooms.find((cr) => cr.id === classroomId)) { 35 | return await next(); 36 | } 37 | 38 | ctx.unauthorized(`You're not allowed to perform this action!`); 39 | }; 40 | -------------------------------------------------------------------------------- /server/api/save/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/saves", 6 | "handler": "save.find", 7 | "config": { 8 | "policies": [] 9 | } 10 | }, 11 | { 12 | "method": "GET", 13 | "path": "/saves/count", 14 | "handler": "save.count", 15 | "config": { 16 | "policies": [] 17 | } 18 | }, 19 | { 20 | "method": "GET", 21 | "path": "/saves/day/:day", 22 | "handler": "save.findByDay", 23 | "config": { 24 | "policies": ["global::isStudent"] 25 | } 26 | }, 27 | { 28 | "method": "GET", 29 | "path": "/saves/:id", 30 | "handler": "save.findOne", 31 | "config": { 32 | "policies": [] 33 | } 34 | }, 35 | { 36 | "method": "POST", 37 | "path": "/saves", 38 | "handler": "save.create", 39 | "config": { 40 | "policies": [] 41 | } 42 | }, 43 | { 44 | "method": "PUT", 45 | "path": "/saves/:id", 46 | "handler": "save.update", 47 | "config": { 48 | "policies": [] 49 | } 50 | }, 51 | { 52 | "method": "DELETE", 53 | "path": "/saves/:id", 54 | "handler": "save.delete", 55 | "config": { 56 | "policies": [] 57 | } 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /server/config/plugins.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config({ path: '../.env' }); 2 | const Sentry = require('@sentry/node'); 3 | const Tracing = require('@sentry/tracing'); 4 | 5 | module.exports = () => ({ 6 | email: { 7 | provider: 'nodemailer', 8 | providerOptions: { 9 | host: 'smtp-relay.sendinblue.com', 10 | port: 587, 11 | auth: { 12 | user: process.env.EMAIL_SMTP_USER, 13 | pass: process.env.EMAIL_SMTP_PASS, 14 | }, 15 | }, 16 | settings: { 17 | defaultFrom: 'no-reply@casmm.org', 18 | defaultReplyTo: 'no-reply@casmm.org', 19 | }, 20 | }, 21 | sentry: { 22 | dsn: process.env.SENTRY_DNS || '', 23 | integrations: [new Sentry.Integrations.Http({ tracing: true })], 24 | tracesSampleRate: 1.0, 25 | }, 26 | upload: { 27 | config: { 28 | provider: 'local', // or another provider like 'aws-s3' 29 | providerOptions: { 30 | //sizeLimit: 1000000, // Set a file size limit in bytes (optional) 31 | // Additional provider-specific configuration, e.g., AWS S3 credentials 32 | local: { 33 | path: './public/uploads', 34 | keepExtensions: true, // Preserve file extensions during upload 35 | }, 36 | }, 37 | actionOptions: { 38 | upload: {}, 39 | delete: {}, 40 | }, 41 | }, 42 | }, 43 | }); 44 | -------------------------------------------------------------------------------- /client/src/views/Mentor/Classroom/Roster/AddStudents/AddStudents.less: -------------------------------------------------------------------------------- 1 | @import '../../../../../assets/style.less'; 2 | 3 | #add-students { 4 | #manual-input { 5 | input[type='text'] { 6 | display: block; 7 | background: none; 8 | margin: 20px auto 0px; 9 | border: 1px solid; 10 | border-color: #colors[secondary]; 11 | padding: 10px 10px; 12 | width: 40%; 13 | outline: none; 14 | color: #colors[text-primary]; 15 | border-radius: 8px; 16 | transition: 0.25s; 17 | } 18 | } 19 | 20 | input[type='button'] { 21 | display: flex; 22 | justify-content: center; 23 | align-items: center; 24 | background: #colors[quaternary]; 25 | width: 40%; 26 | height: 6vh; 27 | margin: 0 auto; 28 | border: none; 29 | color: #colors[text-primary]; 30 | font-size: 1em; 31 | font-weight: 500; 32 | border-radius: 30px; 33 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.377); 34 | cursor: pointer; 35 | transition: 0.25s; 36 | 37 | &:hover { 38 | width: 42%; 39 | background: #colors[quinary]; 40 | } 41 | } 42 | 43 | #emoji-picker { 44 | width: 40%; 45 | margin: 2vh auto; 46 | 47 | .emoji-picker-react { 48 | width: 100%; 49 | } 50 | .emoji-categories { 51 | justify-content: space-around; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /client/src/views/Mentor/Classroom/Roster/AddStudents/AddStudentsModal.jsx: -------------------------------------------------------------------------------- 1 | import {Modal, Button} from 'antd'; 2 | import React, {useState} from "react"; 3 | import AddStudents from "./AddStudents"; 4 | 5 | export default function AddStudentsModal(props) { 6 | const [visible, setVisible] = useState(false); 7 | const {classroomId, addStudentsToTable} = props; 8 | 9 | const showModal = () => { 10 | setVisible(true) 11 | }; 12 | 13 | const handleCancel = () => { 14 | setVisible(false) 15 | }; 16 | 17 | const handleOk = () => { 18 | setVisible(false) 19 | }; 20 | 21 | return ( 22 | 40 | ); 41 | } -------------------------------------------------------------------------------- /server/api/classroom/services/classroom.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // find a classroom by code 4 | module.exports.findByCode = async (code) => { 5 | 6 | // get the whole classroom model 7 | const model = await strapi.query('classroom').model.where('code', code).fetch() 8 | 9 | // return the model and the classroom 10 | return { 11 | model, 12 | classroom: model ? model.toJSON() : undefined 13 | } 14 | } 15 | 16 | const randomCode = (n) => { 17 | 18 | let code = '' 19 | for (n; n > 0; n--) 20 | code += Math.floor(Math.random()*10) 21 | return code 22 | } 23 | 24 | // TODO: add a maximum number of tries 25 | // generate a code that is unique amongst classroom 26 | module.exports.getUniqueCode = async (currentCode = undefined) => { 27 | 28 | let code = currentCode 29 | let codeExists 30 | do { 31 | // allow an existing code to be passed, 32 | // checking if it active and returning 33 | // a new code if it is being used 34 | if (!code) { 35 | // get a four digit code 36 | code = randomCode(4) 37 | } 38 | 39 | // check if a classroom is using the code 40 | const classroom = await strapi.query('classroom').findOne({ code }) 41 | codeExists = classroom !== null 42 | } 43 | while (codeExists) 44 | return code 45 | } -------------------------------------------------------------------------------- /client/src/views/TeacherLogin/ConfirmEmail.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { useLocation, useParams } from 'react-router-dom'; 3 | import { confirmEmail } from '../../Utils/requests'; 4 | import NavBar from '../../components/NavBar/NavBar'; 5 | import './Sorry.less'; 6 | 7 | 8 | export default function ConfirmEmail() { 9 | const search = useLocation().search; 10 | const [code, setCode] = useState(null); 11 | const {confirmationCode} = useParams(); 12 | useEffect(() => { 13 | 14 | setCode(confirmationCode); // Store the code in state 15 | //console.log(confirmationCode); // Debug: Check if the code is extracted correctly 16 | 17 | // If a code exists, make the API call 18 | if (confirmationCode) { 19 | confirmEmail(confirmationCode) 20 | .then((response) => { 21 | console.log(response); 22 | }) 23 | .catch((error) => { 24 | console.error("Error confirming email:", error); 25 | }); 26 | } 27 | }, [search]); // Only trigger when the search string changes 28 | return ( 29 |
30 | 31 |
32 |

Thank you for confirming!

33 | 34 |
35 |
36 | ); 37 | } -------------------------------------------------------------------------------- /client/src/views/Researcher/Report.jsx: -------------------------------------------------------------------------------- 1 | import NavBar from '../../components/NavBar/NavBar'; 2 | import RouteButton from '../../components/RouteButton/RouteButton'; 3 | import React, { useState, useEffect } from 'react'; 4 | import { Link } from 'react-router-dom'; 5 | import './Report.less'; 6 | 7 | export default function Report(props) { 8 | 9 | return ( 10 |
11 | 12 |
Welcome Researcher!
13 |

Reports

14 | 15 |
16 | {/*
17 |
Home
18 |
Reports
19 |
*/} 20 | 21 | 28 | 29 | 30 | 37 | 38 |
39 |
40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /client/src/views/BugReport/BugReport.less: -------------------------------------------------------------------------------- 1 | @import '../../assets/style.less'; 2 | 3 | #bug-report-wrapper { 4 | margin: 12vh auto 8vh auto; 5 | display: inline-block; 6 | } 7 | 8 | #bug-report-title { 9 | background-color: #colors[secondary]; 10 | border-radius: 80px; 11 | width: 80%; 12 | min-height: 8%; 13 | color: #colors[text-secondary]; 14 | font-size: 1.4em; 15 | font-weight: bold; 16 | position: relative; 17 | top: 25px; 18 | left: -40px; 19 | line-height: 50px; 20 | text-align: center; 21 | } 22 | 23 | #bug-report-form { 24 | width: 400px; 25 | padding: 40px 20px; 26 | background-color: #colors[tertiary]; 27 | text-align: center; 28 | border-radius: 15px; 29 | border: 4px solid; 30 | border-color: #colors[secondary]; 31 | 32 | .ant-btn-primary { 33 | display: flex; 34 | justify-content: center; 35 | align-items: center; 36 | background: #colors[quaternary]; 37 | width: 50%; 38 | // display: block; 39 | margin: 0 auto; 40 | border: 2px solid; 41 | border-color: #colors[secondary]; 42 | padding: 14px 40px; 43 | outline: none; 44 | color: #colors[text-primary]; 45 | font-size: 1.2em; 46 | font-weight: 500; 47 | border-radius: 30px; 48 | position: relative; 49 | bottom: -90px; 50 | 51 | &:hover { 52 | background-color: #colors[quinary]; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /server/api/session/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/sessions", 6 | "handler": "session.find", 7 | "config": { 8 | "policies": [] 9 | } 10 | }, 11 | { 12 | "method": "GET", 13 | "path": "/sessions/count", 14 | "handler": "session.count", 15 | "config": { 16 | "policies": [] 17 | } 18 | }, 19 | { 20 | "method": "GET", 21 | "path": "/sessions/:id", 22 | "handler": "session.findOne", 23 | "config": { 24 | "policies": [] 25 | } 26 | }, 27 | { 28 | "method": "POST", 29 | "path": "/sessions", 30 | "handler": "session.create", 31 | "config": { 32 | "policies": [] 33 | } 34 | }, 35 | { 36 | "method": "PUT", 37 | "path": "/sessions/:id", 38 | "handler": "session.update", 39 | "config": { 40 | "policies": [] 41 | } 42 | }, 43 | { 44 | "method": "PUT", 45 | "path": "/sessions/arduino/:id", 46 | "handler": "session.arduinoUpdate", 47 | "config": { 48 | "policies": [] 49 | } 50 | }, 51 | { 52 | "method": "DELETE", 53 | "path": "/sessions/:id", 54 | "handler": "session.delete", 55 | "config": { 56 | "policies": [] 57 | } 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@loadable/component": "^5.15.2", 7 | "antd": "^4.24.8", 8 | "axios": "^1.4.0", 9 | "cross-env": "^7.0.3", 10 | "emoji-picker-react": "^3.6.1", 11 | "http-server": "^0.12.3", 12 | "less": "^4.1.3", 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0", 15 | "react-hooks-global-state": "^2.1.0", 16 | "react-media-library": "^2.0.3", 17 | "react-media-recorder": "^1.7.1", 18 | "react-papaparse": "^3.17.1", 19 | "react-router-dom": "^6.9.0", 20 | "recharts": "^2.5.0", 21 | "vite": "^4.2.0", 22 | "yarn": "^1.22.21" 23 | }, 24 | "scripts": { 25 | "start": "vite", 26 | "start-build": "http-server build -p 3000", 27 | "build": "vite build", 28 | "serve": "vite serve" 29 | }, 30 | "eslintConfig": { 31 | "extends": "react-app" 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | }, 45 | "devDependencies": { 46 | "@types/w3c-web-serial": "^1.0.3", 47 | "@vitejs/plugin-react-swc": "^3.2.0", 48 | "parse-full-name": "^1.2.6" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /server/api/learning-component-types/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/learning-component-types", 6 | "handler": "learning-component-types.find", 7 | "config": { 8 | "policies": [] 9 | } 10 | }, 11 | { 12 | "method": "GET", 13 | "path": "/learning-component-types/count", 14 | "handler": "learning-component-types.count", 15 | "config": { 16 | "policies": [] 17 | } 18 | }, 19 | { 20 | "method": "GET", 21 | "path": "/learning-component-types/:id", 22 | "handler": "learning-component-types.findOne", 23 | "config": { 24 | "policies": [] 25 | } 26 | }, 27 | { 28 | "method": "POST", 29 | "path": "/learning-component-types", 30 | "handler": "learning-component-types.create", 31 | "config": { 32 | "policies": [] 33 | } 34 | }, 35 | { 36 | "method": "PUT", 37 | "path": "/learning-component-types/:id", 38 | "handler": "learning-component-types.update", 39 | "config": { 40 | "policies": [] 41 | } 42 | }, 43 | { 44 | "method": "DELETE", 45 | "path": "/learning-component-types/:id", 46 | "handler": "learning-component-types.delete", 47 | "config": { 48 | "policies": [] 49 | } 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /client/src/assets/style.less: -------------------------------------------------------------------------------- 1 | 2 | #colors() { 3 | primary: #3D5C82; 4 | secondary: #5BABDE; 5 | tertiary: #F4F4F5; 6 | quaternary: #F3D250; 7 | quinary:#F1C307; 8 | senary: #E9E9E9; 9 | text-primary: #414141; 10 | text-secondary: #FFFFFF; 11 | } 12 | 13 | .flex { 14 | display: flex; 15 | } 16 | 17 | .flex-column { 18 | flex-direction: column; 19 | } 20 | 21 | .flex-row { 22 | flex-direction: row; 23 | } 24 | 25 | .space-between { 26 | justify-content: space-between; 27 | } 28 | 29 | .justify-end { 30 | justify-content: flex-end; 31 | } 32 | 33 | .justify-center { 34 | justify-content: center; 35 | } 36 | 37 | .container { 38 | height: 100%; 39 | width: 100%; 40 | background-color: #colors[primary]; 41 | min-height: 100vh; 42 | overflow-x: hidden; 43 | } 44 | .container::-webkit-scrollbar { 45 | display: none; 46 | } 47 | 48 | .vertical-container { 49 | margin: 1.5em; 50 | } 51 | 52 | .hvr-info { 53 | cursor: pointer; 54 | } 55 | 56 | .card { 57 | background-color: #colors[tertiary]; 58 | padding: .7em; 59 | } 60 | 61 | .overflow-visible { 62 | overflow: visible; 63 | } 64 | 65 | .overflow-hidden { 66 | overflow: hidden; 67 | } 68 | 69 | .nav-padding { 70 | padding-top: 8vh; 71 | } 72 | 73 | .replayButton { 74 | cursor: pointer; 75 | } 76 | 77 | .bold { 78 | font-weight: bold; 79 | } -------------------------------------------------------------------------------- /client/src/views/Home/HomeJoin.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import { message } from 'antd'; 4 | import './Home.less'; 5 | import { getStudents } from '../../Utils/requests'; 6 | 7 | export default function HomeJoin(props) { 8 | const [loading, setLoading] = useState(false); 9 | const [joinCode, setJoinCode] = useState(''); 10 | const navigate = useNavigate(); 11 | const handleLogin = () => { 12 | setLoading(true); 13 | 14 | getStudents(joinCode).then((res) => { 15 | if (res.data) { 16 | setLoading(false); 17 | localStorage.setItem('join-code', joinCode); 18 | navigate('/login'); 19 | } else { 20 | setLoading(false); 21 | message.error('Join failed. Please input a valid join code.'); 22 | } 23 | }); 24 | }; 25 | 26 | return ( 27 |
{ 30 | if (e.key === 'Enter') handleLogin(); 31 | }} 32 | > 33 | setJoinCode(e.target.value)} 38 | /> 39 | 45 |
46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /server/config/database.js: -------------------------------------------------------------------------------- 1 | /** 2 | * parses a PostgreSQL DB connection URL into the parts needed 3 | * by Strapi. Without this, heroku may throw ECONNREFUSED 127.0.0.1:xxxx. 4 | */ 5 | 6 | const url = require('url') 7 | 8 | if (process.env.DATABASE_URL) { 9 | const parsed = url.parse(process.env.DATABASE_URL, true) 10 | const [username, password] = parsed.auth.split(':') 11 | 12 | process.env.DATABASE_HOST = parsed.hostname 13 | process.env.DATABASE_PORT = Number(parsed.port) 14 | process.env.DATABASE_NAME = parsed.pathname.substr(1) 15 | process.env.DATABASE_USERNAME = username 16 | process.env.DATABASE_PASSWORD = password 17 | } 18 | 19 | module.exports = ({ env }) => ({ 20 | defaultConnection: 'default', 21 | connections: { 22 | default: { 23 | connector: 'bookshelf', 24 | settings: { 25 | client: 'postgres', 26 | host: env('DATABASE_HOST', 'localhost'), 27 | port: env.int('DATABASE_PORT', 5432), 28 | database: env('DATABASE_NAME', 'strapi'), 29 | username: env('DATABASE_USERNAME', 'postgres'), 30 | password: env('DATABASE_PASSWORD', 'postgres'), 31 | schema: 'public', 32 | ssl: {rejectUnauthorized: false}, 33 | }, 34 | options: { 35 | 'pool': { 36 | 'min': 0, 37 | 'max': 15, 38 | 'idleTimeoutMillis': 30000, 39 | 'createTimeoutMillis': 30000, 40 | 'acquireTimeoutMillis': 30000 41 | } 42 | } 43 | }, 44 | }, 45 | }) -------------------------------------------------------------------------------- /server/api/day/models/day.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "days", 4 | "info": { 5 | "name": "Day", 6 | "description": "" 7 | }, 8 | "options": { 9 | "increments": true, 10 | "timestamps": true, 11 | "draftAndPublish": false 12 | }, 13 | "attributes": { 14 | "learning_standard": { 15 | "via": "days", 16 | "model": "learning-standard" 17 | }, 18 | "number": { 19 | "type": "biginteger", 20 | "required": true 21 | }, 22 | "template": { 23 | "type": "text", 24 | "required": true 25 | }, 26 | "template_code": { 27 | "type": "text", 28 | "required": false 29 | }, 30 | "template_code_answer": { 31 | "type": "text", 32 | "required": false 33 | }, 34 | "blocks": { 35 | "collection": "block" 36 | }, 37 | "description": { 38 | "type": "text", 39 | "required":false 40 | }, 41 | "TekS": { 42 | "type": "string", 43 | "required": false 44 | }, 45 | "images": { 46 | "type": "text" 47 | }, 48 | "link": { 49 | "type": "string", 50 | "required": false 51 | }, 52 | "learning_components": { 53 | "via": "days", 54 | "collection": "learning-components" 55 | }, 56 | "activity_template": { 57 | "type": "text", 58 | "required": false 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /server/api/mentor/controllers/mentor.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { sanitizeEntity } = require("strapi-utils/lib") 4 | 5 | module.exports = { 6 | 7 | async me(ctx) { 8 | 9 | // get the mentor that is currently logged in 10 | const { user } = ctx.state 11 | const mentor = await strapi.services.mentor.findOne({ user: user.id }) 12 | 13 | // remove private fields and return the mentor 14 | return sanitizeEntity(mentor, { model: strapi.models.mentor }) 15 | }, 16 | 17 | /** 18 | * Create a new mentor 19 | * 20 | * @param {String} first_name 21 | * @param {String} last_name 22 | * 23 | * @return {Mentor} 24 | */ 25 | async create(ctx) { 26 | 27 | // ensure the request is in the right format 28 | if (ctx.is('multipart')) return ctx.badRequest( 29 | 'Multipart requests are not accepted!', 30 | { id: 'Mentor.create.format.invalid', error: 'ValidationError' } 31 | ) 32 | 33 | // get the user that is currently logged in 34 | // to set as the new mentor's user 35 | const { user } = ctx.state 36 | ctx.request.body.user = user.id 37 | 38 | // remove private fields and return the new mentor 39 | const mentor = await strapi.services.mentor.create(ctx.request.body) 40 | return sanitizeEntity(mentor, { model: strapi.models.mentor }) 41 | } 42 | } -------------------------------------------------------------------------------- /client/public/lib/readme.md: -------------------------------------------------------------------------------- 1 | ## About This Directory 2 | This directory is used to store the libraries being used for this web app. Notably, the [ArduBlockly](https://github.com/carlosperate/ardublockly) library, which relies on Google's [Blockly](https://github.com/google/blockly) library. The files are referenced in `index.html`. 3 | 4 | These compressed files are generated with ArduBlockly's build script. Since ArduBlockly have ended development since 2018, we've made our own branch of ArduBlockly, as seen [here](https://github.com/STEM-C/ardublockly), where we've made bug fixes and added additional support for different blocks. 5 | 6 | Details about building the compressed files and adding to our ArduBlockly library can be seen [here](https://github.com/STEM-C/ardublockly/wiki/Working-with-the-ArduBlockly-Library). 7 | 8 | ## depreciated.js 9 | Inside this file, depreciated code from google's closure library can be found. 10 | 11 | The Blockly library that ArduBlockly is based off of makes use of Google's closure library. During the build process, the most recent version of the closure library is pulled. Since the original ArduBlockly ended development in 2018, and the version of Blockly that it's based off of came from 2016, a number of elements in the codebase have been depreciated according to Google's code base. 12 | 13 | By keeping these depreciated code in this file, we ensure that our version of Blockly continues to function. -------------------------------------------------------------------------------- /compile/src/handlers/worker.js: -------------------------------------------------------------------------------- 1 | const { processJob } = require("../controllers/job") 2 | const { compileLog } = require("../utils/base") 3 | 4 | const Queue = require("bull") 5 | const redisUrlParse = require("redis-url-parse") 6 | 7 | // Connect to a local redis instance locally, and the Heroku-provided URL in production 8 | const REDIS_URL = process.env.REDIS_URL || "redis://compile_queue:6379" 9 | const { host, port, password } = redisUrlParse(REDIS_URL) 10 | const bullOptions = REDIS_URL.includes("rediss://") 11 | ? { 12 | redis: { 13 | port: Number(port), 14 | host, 15 | password, 16 | tls: { 17 | rejectUnauthorized: false, 18 | requestCert: true, 19 | }, 20 | }, 21 | } 22 | : REDIS_URL 23 | // The maximum number of jobs each worker should process at once 24 | // Each job is CPU-intensive, so this value should not be too high 25 | const maxJobsPerWorker = process.env.JOB_CONCURRENCY || 1 26 | 27 | module.exports.init = () => { 28 | // starting up the service 29 | compileLog("Starting compile cluster") 30 | } 31 | 32 | module.exports.start = id => { 33 | // Signal worked started 34 | compileLog(`Started worker ${id}`) 35 | 36 | // Connect to the named queue 37 | const compile_queue = new Queue("submissions", bullOptions) 38 | 39 | // start processing jobs from the submission queue 40 | compile_queue.process(maxJobsPerWorker, processJob) 41 | } 42 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "private": true, 4 | "version": "0.1.0", 5 | "description": "A REST API and admin portal", 6 | "scripts": { 7 | "develop": "strapi develop", 8 | "start": "strapi start", 9 | "build": "strapi build", 10 | "strapi": "strapi", 11 | "build-client": "rm -rf ./public/client/* && cd ../client && env PUBLIC_URL=/client yarn build && mv ./build/* ../server/public/client/" 12 | }, 13 | "dependencies": { 14 | "bull": "^4.10.0", 15 | "dotenv": "^10.0.0", 16 | "knex": "0.21.18", 17 | "pg": "^8.10.0", 18 | "redis-url-parse": "^2.0.0", 19 | "sharp": "^0.31.3", 20 | "strapi": "^3.6.11", 21 | "strapi-admin": "^3.6.11", 22 | "strapi-connector-bookshelf": "^3.6.11", 23 | "strapi-plugin-content-manager": "^3.6.11", 24 | "strapi-plugin-content-type-builder": "^3.6.11", 25 | "strapi-plugin-documentation": "^3.6.11", 26 | "strapi-plugin-email": "^3.6.11", 27 | "strapi-plugin-sentry": "^3.6.11", 28 | "strapi-plugin-upload": "^3.6.11", 29 | "strapi-plugin-users-permissions": "^3.6.11", 30 | "strapi-provider-email-nodemailer": "^3.6.11", 31 | "strapi-utils": "^3.6.11" 32 | }, 33 | "author": { 34 | "name": "Nicholas Ionata, Siyu Chen" 35 | }, 36 | "strapi": { 37 | "uuid": "d9c4d9b6-4733-4e82-80ee-0a812c951edb" 38 | }, 39 | "engines": { 40 | "node": ">=10.0.0", 41 | "npm": ">=6.0.0" 42 | }, 43 | "license": "MIT" 44 | } 45 | -------------------------------------------------------------------------------- /.github/workflows/start-review.yml: -------------------------------------------------------------------------------- 1 | name: Create Review App 2 | on: 3 | pull_request: 4 | branches: [develop] 5 | types: [opened, reopened] 6 | jobs: 7 | run: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout the repo 11 | uses: actions/checkout@v2 12 | - name: Create review app 13 | id: app 14 | uses: STEM-C/auto/review@v0.7.2 15 | with: 16 | base: review 17 | pipeline: ${{ secrets.PIPELINE_ID }} 18 | token: ${{ secrets.HEROKU_TOKEN }} 19 | - name: Import development database 20 | run: ./scripts/init_db.sh 21 | env: 22 | ENVIRONMENT: development 23 | DATABASE_URL: ${{ steps.app.outputs.database_url }} 24 | SCRIPT_PATH: ./scripts 25 | - name: Built, test, and deploy app 26 | uses: STEM-C/auto/build-test-deploy@v0.7.2 27 | with: 28 | image_tag: ${{ steps.app.outputs.app_name }} 29 | app_name: ${{ steps.app.outputs.app_name }} 30 | github_token: ${{ secrets.GITHUB_TOKEN }} 31 | env: 32 | HEROKU_API_KEY: ${{ secrets.HEROKU_TOKEN }} 33 | - uses: chrnorm/deployment-action@releases/v1 34 | name: Create GitHub deployment 35 | if: success() 36 | id: deployment 37 | with: 38 | initial_status: 'success' 39 | token: ${{ github.token }} 40 | target_url: https://${{ steps.app.outputs.app_name }}.herokuapp.com 41 | environment: ${{ steps.app.outputs.app_name }} 42 | -------------------------------------------------------------------------------- /server/api/cc-workspace/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/cc-workspaces", 6 | "handler": "cc-workspace.find", 7 | "config": { 8 | "policies": [] 9 | } 10 | }, 11 | { 12 | "method": "GET", 13 | "path": "/cc-workspaces/count", 14 | "handler": "cc-workspace.count", 15 | "config": { 16 | "policies": [] 17 | } 18 | }, 19 | { 20 | "method": "GET", 21 | "path": "/cc-workspaces/:id", 22 | "handler": "cc-workspace.findOne", 23 | "config": { 24 | "policies": ["isContentCreatorOrHasClassroom"] 25 | } 26 | }, 27 | { 28 | "method": "GET", 29 | "path": "/cc-workspaces/toolbox/:id", 30 | "handler": "cc-workspace.toolbox", 31 | "config": { 32 | "policies": [] 33 | } 34 | }, 35 | { 36 | "method": "POST", 37 | "path": "/cc-workspaces", 38 | "handler": "cc-workspace.create", 39 | "config": { 40 | "policies": ["isContentCreatorOrHasClassroom"] 41 | } 42 | }, 43 | { 44 | "method": "PUT", 45 | "path": "/cc-workspaces/:id", 46 | "handler": "cc-workspace.update", 47 | "config": { 48 | "policies": ["isContentCreatorOrHasClassroom"] 49 | } 50 | }, 51 | { 52 | "method": "DELETE", 53 | "path": "/cc-workspaces/:id", 54 | "handler": "cc-workspace.delete", 55 | "config": { 56 | "policies": ["isContentCreatorOrHasClassroom"] 57 | } 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /client/src/views/TeacherLogin/Sorry.jsx: -------------------------------------------------------------------------------- 1 | import {React, useEffect} from "react" 2 | import NavBar from "../../components/NavBar/NavBar" 3 | import "./Sorry.less" 4 | import { getSupers } from "../../Utils/AuthRequests" 5 | import { sendEmailConfirmationEmail } from "../../Utils/requests" 6 | 7 | 8 | export default function Sorry(props) { 9 | const superEmails = [] 10 | const myUser = JSON.parse(sessionStorage.user) 11 | 12 | useEffect( () => { 13 | 14 | console.log(myUser) 15 | getSupers() 16 | .then(response => { 17 | //console.log(response.data) 18 | for (var i = 0; i < response.data.length; i++){ 19 | if (response.data[i].isActive) { 20 | superEmails.push(response.data[i].email); 21 | }; 22 | //console.log(superEmails) 23 | }; 24 | }) 25 | 26 | }, []) 27 | 28 | const resendEmail = () => { 29 | for (var i = 0; i < superEmails.length; i++){ 30 | sendEmailConfirmationEmail(myUser.email, superEmails[i]) 31 | .then(response => { 32 | //console.log('hihihi'); 33 | }) 34 | } 35 | } 36 | 37 | return ( 38 |
39 | 40 |
41 |

Hey! Looks like your account is not confirmed. Please ask a "STRAPI SUPER ADMIN" to 42 | check their email for a confirmation code.

43 |

If you wish to resend a confirmation email, click here

44 | 45 |
46 |
47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /client/vite.config.js: -------------------------------------------------------------------------------- 1 | import react from "@vitejs/plugin-react-swc" 2 | import { defineConfig } from "vite" 3 | 4 | const base = process.env.PUBLIC_URL ?? "/" 5 | console.log(base); 6 | 7 | export default defineConfig({ 8 | css: { 9 | preprocessorOptions: { 10 | less: { 11 | modifyVars: { 12 | "@primary-color": "#3d5c82", // primary color for all components 13 | "@link-color": "#3d5c82", // link color 14 | "@success-color": "#52c41a", // success state color 15 | "@warning-color": "#faad14", // warning state color 16 | "@error-color": "#f5222d", // error state color 17 | "@font-size-base": "14px", // major text font size 18 | "@heading-color": "rgba(0, 0, 0, 0.85)", // heading text color 19 | "@text-color": "rgba(0, 0, 0, 0.65)", // major text color 20 | "@text-color-secondary": "rgba(0, 0, 0, 0.45)", // secondary text color 21 | "@disabled-color": "rgba(0, 0, 0, 0.25)", // disable state color 22 | "@border-radius-base": "4px", // major border radius 23 | "@border-color-base": "#d9d9d9", // major border color 24 | "@box-shadow-base": "0 2px 8px rgba(0, 0, 0, 0.15)", // major shadow for layers 25 | }, 26 | javascriptEnabled: true, 27 | }, 28 | }, 29 | }, 30 | //in build mode, the output will be copoied to server/puiblic/client 31 | base, 32 | plugins: [react()], 33 | server: { 34 | port: 3000, 35 | proxy: { 36 | "/api": { 37 | target: "http://localhost:1337", 38 | }, 39 | }, 40 | }, 41 | build: { 42 | outDir: "build", 43 | }, 44 | }) 45 | -------------------------------------------------------------------------------- /client/src/views/Replay/Replay.less: -------------------------------------------------------------------------------- 1 | @import '../../assets/style.less'; 2 | 3 | #timeline-container { 4 | display: flex; 5 | justify-content: center; 6 | } 7 | 8 | #timeline { 9 | height: 1px; 10 | background: rgb(255, 255, 255); 11 | margin: 60px 20px; 12 | width: 100%; 13 | display: flex; 14 | align-items: center; 15 | justify-content: space-around; 16 | font-size: 13px; 17 | } 18 | 19 | .current-time { 20 | display: flex; 21 | justify-content: center; 22 | align-items: center; 23 | font-weight: bold; 24 | color: #colors[quaternary]; 25 | background-color: #colors[primary]; 26 | transform: rotate(45deg); 27 | width: auto; 28 | height: 19px; 29 | border-radius: 10px; 30 | } 31 | 32 | .all-times { 33 | color: white; 34 | background-color: #colors[primary]; 35 | transform: rotate(45deg); 36 | &:hover { 37 | cursor: pointer; 38 | } 39 | } 40 | 41 | #description-container { 42 | border-radius: 10px; 43 | top: 10px; 44 | } 45 | 46 | #logs-title { 47 | font-size: 18px; 48 | } 49 | 50 | .playspeed-slider { 51 | width: 100px; 52 | .ant-slider-rail, 53 | .ant-slider-track { 54 | &, 55 | &:hover { 56 | background-color: #colors[secondary] !important; 57 | } 58 | } 59 | } 60 | 61 | #action-title{ 62 | color: rgb(19, 18, 18); 63 | font-size: larger; 64 | font-weight: 600; 65 | } 66 | 67 | #replay-log{ 68 | 69 | tbody > tr > td { 70 | background: none; 71 | } 72 | .table-row { 73 | cursor: pointer; 74 | } 75 | .table-row-light { 76 | background-color: #ffffff; 77 | } 78 | .table-row-dark { 79 | background-color: #cecece; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /client/src/components/MentorSubHeader/MentorSubHeader.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import AddStudentsModal from '../../views/Mentor/Classroom/Roster/AddStudents/AddStudentsModal'; 4 | import './MentorSubHeader.less'; 5 | 6 | export default function MentorSubHeader(props) { 7 | const { 8 | title, 9 | addActivityActive, 10 | addUserActive, 11 | classroomId, 12 | cardViewActive, 13 | listViewActive, 14 | checkoutActive, 15 | setListView, 16 | addStudentsToTable, 17 | } = props; 18 | 19 | return ( 20 | 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /client/src/views/Workspace/Workspace.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { getDayToolbox } from '../../Utils/requests.js'; 3 | import BlocklyCanvasPanel from '../../components/DayPanels/BlocklyCanvasPanel/BlocklyCanvasPanel'; 4 | import { message } from 'antd'; 5 | import NavBar from '../../components/NavBar/NavBar'; 6 | import { useNavigate } from 'react-router-dom'; 7 | 8 | export default function Workspace({ handleLogout }) { 9 | const [day, setDay] = useState({}); 10 | 11 | useEffect(() => { 12 | const localDay = JSON.parse(localStorage.getItem('my-day')); 13 | const navigate = useNavigate(); 14 | 15 | if (localDay) { 16 | if (localDay.toolbox) { 17 | setDay(localDay); 18 | } else { 19 | getDayToolbox(localDay.id).then((res) => { 20 | if (res.data) { 21 | let loadedDay = { ...localDay, toolbox: res.data.toolbox }; 22 | 23 | localStorage.setItem('my-day', JSON.stringify(loadedDay)); 24 | setDay(loadedDay); 25 | } else { 26 | message.error(res.err); 27 | } 28 | }); 29 | } 30 | } else { 31 | navigate(-1); 32 | } 33 | }, []); 34 | 35 | const handleGoBack = () => { 36 | navigate(-1); 37 | }; 38 | 39 | return ( 40 |
41 | 42 | 49 |
50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /client/src/views/Mentor/Classroom/Roster/StudentModal.jsx: -------------------------------------------------------------------------------- 1 | import { Modal, Button } from 'antd'; 2 | import React, { useState } from 'react'; 3 | 4 | export default function StudentModal({ linkBtn, student, getFormattedDate }) { 5 | const [visible, setVisible] = useState(false); 6 | 7 | const showModal = () => { 8 | setVisible(true); 9 | }; 10 | 11 | const handleCancel = () => { 12 | setVisible(false); 13 | }; 14 | 15 | const handleOk = () => { 16 | setVisible(false); 17 | }; 18 | 19 | return ( 20 |
21 | 24 | 30 | OK 31 | , 32 | ]} 33 | > 34 | 38 | 51 | 52 |
53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /client/src/Utils/AuthRequests.js: -------------------------------------------------------------------------------- 1 | import { server } from './hosts'; 2 | import { setUserState, getCurrUser } from './userState'; 3 | 4 | import axios from 'axios'; 5 | 6 | // return user token from strapi 7 | export const postUser = async (body) => { 8 | const response = await axios.post(`${server}/auth/local`, body); 9 | return response; 10 | }; 11 | 12 | export const regUser = async (body) => { 13 | const response = await axios.post(`${server}/auth/local/register`, body); 14 | //username: 'testuser', 15 | //email: 'tu@mail.com', 16 | //password: '123456', 17 | 18 | return response; 19 | }; 20 | 21 | 22 | 23 | // return token from session storage 24 | export const getToken = () => { 25 | return sessionStorage.getItem('token') || null; 26 | }; 27 | 28 | // remove the token ans user from the session storage 29 | export const removeUserSession = () => { 30 | sessionStorage.removeItem('token'); 31 | sessionStorage.removeItem('user'); 32 | setUserState(getCurrUser()); 33 | }; 34 | 35 | // set the token and user from the session storage 36 | export const setUserSession = (jwt, user) => { 37 | sessionStorage.setItem('token', jwt); 38 | sessionStorage.setItem('user', user); 39 | setUserState(getCurrUser()); 40 | }; 41 | 42 | export const getSupers = async () => { 43 | const response = await axios.get(`${server}/strapiusers/super-admin`); 44 | 45 | return response; 46 | }; 47 | 48 | export const getConfirmed = async () => { 49 | var thisUser = JSON.parse(sessionStorage.getItem('user')); 50 | return thisUser.confirmed; 51 | }; 52 | 53 | export const getMyRole = async () => { 54 | var thisUser = JSON.parse(sessionStorage.getItem('user')); 55 | return thisUser.role; 56 | }; -------------------------------------------------------------------------------- /client/src/views/Mentor/Classroom/Home/DisplayFormModal.jsx: -------------------------------------------------------------------------------- 1 | import {Modal, Button} from 'antd'; 2 | import React, {useState} from "react"; 3 | import { Form } from 'react-router-dom'; 4 | import './Home.less' 5 | import { updateClassroom } from '../../../../Utils/requests'; 6 | 7 | export default function DisplayFormModal(props) { 8 | const [visible, setVisible] = useState(false); 9 | const {classroom} = props; 10 | const [form, setForm] = useState('') 11 | 12 | const showModal = () => { 13 | setForm('') 14 | setVisible(true) 15 | }; 16 | 17 | const handleCancel = () => { 18 | setVisible(false) 19 | }; 20 | 21 | const handleTyping = (event) => { 22 | 23 | setForm(event.target.value) 24 | } 25 | 26 | const handleOk = () => { 27 | //console.log(form) 28 | classroom.form = form 29 | updateClassroom(classroom.id, classroom.form) 30 | setVisible(false) 31 | }; 32 | 33 | return ( 34 |
35 | 36 | 43 | OK 44 | , 45 | ]} 46 | > 47 | 48 | 49 |
50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /client/src/components/DayPanels/BlocklyCanvasPanel/modals/CodeModal.jsx: -------------------------------------------------------------------------------- 1 | import { Modal, Button, Typography, Menu } from 'antd'; 2 | import React, { useState } from 'react'; 3 | import { getArduino, getXml } from '../../Utils/helpers'; 4 | 5 | export default function CodeModal(props) { 6 | const [visible, setVisible] = useState(false); 7 | const { title, workspaceRef } = props; 8 | const { Text } = Typography; 9 | 10 | const showModal = () => { 11 | setVisible(true); 12 | }; 13 | 14 | const handleCancel = () => { 15 | setVisible(false); 16 | }; 17 | 18 | const handleOk = () => { 19 | setVisible(false); 20 | }; 21 | 22 | return ( 23 |
24 | {title === 'XML' ? ( 25 | 26 | 27 |  Show XML 28 | 29 | ) : ( 30 | 31 | 32 |  Show Arduino Code 33 | 34 | )} 35 | 42 | OK 43 | , 44 | ]} 45 | > 46 | {workspaceRef ? ( 47 |
48 | 49 | {title === 'XML' 50 | ? getXml(workspaceRef, false) 51 | : getArduino(workspaceRef, false)} 52 | 53 |
54 | ) : null} 55 |
56 |
57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /server/api/classroom-manager/documentation/1.0.0/classroom-manager.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": { 3 | "/classroom-managers/me": { 4 | "get": { 5 | "deprecated": false, 6 | "description": "", 7 | "responses": { 8 | "200": { 9 | "description": "response", 10 | "content": { 11 | "application/json": { 12 | "schema": { 13 | "properties": { 14 | "foo": { 15 | "type": "string" 16 | } 17 | } 18 | } 19 | } 20 | } 21 | }, 22 | "403": { 23 | "description": "Forbidden", 24 | "content": { 25 | "application/json": { 26 | "schema": { 27 | "$ref": "#/components/schemas/Error" 28 | } 29 | } 30 | } 31 | }, 32 | "404": { 33 | "description": "Not found", 34 | "content": { 35 | "application/json": { 36 | "schema": { 37 | "$ref": "#/components/schemas/Error" 38 | } 39 | } 40 | } 41 | }, 42 | "default": { 43 | "description": "unexpected error", 44 | "content": { 45 | "application/json": { 46 | "schema": { 47 | "$ref": "#/components/schemas/Error" 48 | } 49 | } 50 | } 51 | } 52 | }, 53 | "summary": "", 54 | "tags": [ 55 | "Classroom-manager" 56 | ], 57 | "parameters": [] 58 | } 59 | } 60 | }, 61 | "tags": [] 62 | } -------------------------------------------------------------------------------- /server/middlewares/proxy/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies 5 | */ 6 | const fs = require('fs'); 7 | const path = require('path'); 8 | 9 | module.exports = (strapi) => { 10 | return { 11 | /** 12 | * Initialize the hook 13 | */ 14 | 15 | initialize() { 16 | strapi.app.use(async (ctx, next) => { 17 | const reqPath = ctx.request.path; 18 | const reqHost = ctx.request.header.host; 19 | const reqReferer = ctx.request.header.referer; 20 | const refererUrl = reqReferer 21 | ? reqReferer 22 | .replace('http://', '') 23 | .replace('https://', '') 24 | .replace(reqHost, '') 25 | : ''; 26 | 27 | if ( 28 | reqPath === '/favicon.ico' || 29 | reqPath.startsWith('/admin') || 30 | reqPath.startsWith('/client') || 31 | refererUrl.startsWith('/admin') || 32 | reqPath.startsWith('/documentation') || 33 | refererUrl.startsWith('/documentation') 34 | ) { 35 | // if request for favicon, admin, or client or if request from admin, go next 36 | await next(); 37 | } else if (reqPath.startsWith('/api')) { 38 | // if api request, remove /api 39 | ctx.request.path = reqPath.replace('/api', ''); 40 | await next(); 41 | } else { 42 | // serve the index.html for the client route 43 | const { clientPath } = strapi.config.middleware.settings.proxy; 44 | const clientDir = path.resolve(strapi.dir, clientPath); 45 | 46 | ctx.type = 'html'; 47 | ctx.body = fs.createReadStream(path.join(clientDir + '/index.html')); 48 | } 49 | }); 50 | }, 51 | }; 52 | }; 53 | -------------------------------------------------------------------------------- /server/api/strapiusers/documentation/1.0.0/admin.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": { 3 | "/admin/super-admin": { 4 | "get": { 5 | "deprecated": false, 6 | "description": "", 7 | "responses": { 8 | "200": { 9 | "description": "response", 10 | "content": { 11 | "application/json": { 12 | "schema": { 13 | "properties": { 14 | "foo": { 15 | "type": "string" 16 | } 17 | } 18 | } 19 | } 20 | } 21 | }, 22 | "403": { 23 | "description": "Forbidden", 24 | "content": { 25 | "application/json": { 26 | "schema": { 27 | "$ref": "#/components/schemas/Error" 28 | } 29 | } 30 | } 31 | }, 32 | "404": { 33 | "description": "Not found", 34 | "content": { 35 | "application/json": { 36 | "schema": { 37 | "$ref": "#/components/schemas/Error" 38 | } 39 | } 40 | } 41 | }, 42 | "default": { 43 | "description": "unexpected error", 44 | "content": { 45 | "application/json": { 46 | "schema": { 47 | "$ref": "#/components/schemas/Error" 48 | } 49 | } 50 | } 51 | } 52 | }, 53 | "summary": "", 54 | "tags": [ 55 | "Admin" 56 | ], 57 | "parameters": [] 58 | } 59 | } 60 | }, 61 | "tags": [ 62 | { 63 | "name": "Admin" 64 | } 65 | ] 66 | } -------------------------------------------------------------------------------- /client/src/views/Mentor/Classroom/Roster/CardView.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import StudentModal from "./StudentModal"; 3 | 4 | export default function CardView(props) { 5 | const {studentData, onEnrollToggle, getFormattedDate} = props; 6 | 7 | return ( 8 |
9 | {studentData.map(student => 10 |
11 |
12 |

{student.character}

13 |

{student.name}

14 |
15 |
16 |
17 |

Last logged in:

18 |

{getFormattedDate(student.last_logged_in)}

19 |

20 |
21 |
22 |

Status:

23 |

{student.enrolled.enrolled ? 'Enrolled' : 'Unenrolled'}

24 |
25 |
26 |
27 | 28 | 33 |
34 |
35 | )} 36 |
37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /server/api/strapiusers/documentation/1.0.0/strapiusers.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": { 3 | "/strapiusers/super-admin": { 4 | "get": { 5 | "deprecated": false, 6 | "description": "", 7 | "responses": { 8 | "200": { 9 | "description": "response", 10 | "content": { 11 | "application/json": { 12 | "schema": { 13 | "properties": { 14 | "foo": { 15 | "type": "string" 16 | } 17 | } 18 | } 19 | } 20 | } 21 | }, 22 | "403": { 23 | "description": "Forbidden", 24 | "content": { 25 | "application/json": { 26 | "schema": { 27 | "$ref": "#/components/schemas/Error" 28 | } 29 | } 30 | } 31 | }, 32 | "404": { 33 | "description": "Not found", 34 | "content": { 35 | "application/json": { 36 | "schema": { 37 | "$ref": "#/components/schemas/Error" 38 | } 39 | } 40 | } 41 | }, 42 | "default": { 43 | "description": "unexpected error", 44 | "content": { 45 | "application/json": { 46 | "schema": { 47 | "$ref": "#/components/schemas/Error" 48 | } 49 | } 50 | } 51 | } 52 | }, 53 | "summary": "", 54 | "tags": [ 55 | "Strapiusers" 56 | ], 57 | "parameters": [] 58 | } 59 | } 60 | }, 61 | "tags": [ 62 | { 63 | "name": "Strapiusers" 64 | } 65 | ] 66 | } -------------------------------------------------------------------------------- /client/src/views/ContentCreator/UnitCreator/UnitCreator.less: -------------------------------------------------------------------------------- 1 | @import '../../../assets/style.less'; 2 | 3 | #class-card { 4 | text-align: left; 5 | width: 35vw; 6 | height: 100%; 7 | margin: 2vw auto 0 auto; 8 | background: #colors[tertiary]; 9 | color: #colors[text-primary]; 10 | border: solid 4px; 11 | border-color: #colors[secondary]; 12 | border-radius: 10px; 13 | } 14 | 15 | #add-unit-btn { 16 | position: relative; 17 | top: -2vh; 18 | width: auto; 19 | max-height: 9vh; 20 | padding: 5px 10px; 21 | font-size: 1em; 22 | font-weight: 500; 23 | border-radius: 30px; 24 | margin-right: 20px; 25 | background: #colors[quaternary]; 26 | border: none; 27 | color: #colors[text-primary]; 28 | transition: 0.25s; 29 | cursor: pointer; 30 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.377); 31 | 32 | &:hover { 33 | background: #colors[quinary]; 34 | } 35 | } 36 | 37 | #add-units { 38 | input, 39 | textarea { 40 | display: block; 41 | margin: 5px 5px 0px; 42 | background: none; 43 | border: 1px solid; 44 | border-color: #colors[secondary]; 45 | padding: 10px 10px; 46 | width: 95%; 47 | outline: none; 48 | color: #colors[text-primary]; 49 | border-radius: 4px; 50 | transition: 0.25s; 51 | } 52 | 53 | label { 54 | margin: 10px auto 12px; 55 | white-space: pre-wrap; 56 | } 57 | } 58 | 59 | #grade-dropdown { 60 | display: block; 61 | background: none; 62 | margin: 5px auto 0px; 63 | border: 1px solid; 64 | border-color: #colors[secondary]; 65 | padding: 14px 10px; 66 | width: 95%; 67 | outline: none; 68 | color: #colors[text-primary]; 69 | border-radius: 4px; 70 | transition: 0.25s; 71 | } 72 | 73 | #grade:focus { 74 | width: 100%; 75 | } 76 | 77 | .content-creator-button { 78 | float: right; 79 | margin: 0px 5px; 80 | width: 8vw; 81 | } 82 | -------------------------------------------------------------------------------- /server/api/block/models/block.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Lifecycle callbacks for the `block` model. 5 | */ 6 | 7 | module.exports = { 8 | // Before saving a value. 9 | // Fired before an `insert` or `update` query. 10 | // beforeSave: async (model, attrs, options) => {}, 11 | 12 | // After saving a value. 13 | // Fired after an `insert` or `update` query. 14 | // afterSave: async (model, response, options) => {}, 15 | 16 | // Before fetching a value. 17 | // Fired before a `fetch` operation. 18 | // beforeFetch: async (model, columns, options) => {}, 19 | 20 | // After fetching a value. 21 | // Fired after a `fetch` operation. 22 | // afterFetch: async (model, response, options) => {}, 23 | 24 | // Before fetching all values. 25 | // Fired before a `fetchAll` operation. 26 | // beforeFetchAll: async (model, columns, options) => {}, 27 | 28 | // After fetching all values. 29 | // Fired after a `fetchAll` operation. 30 | // afterFetchAll: async (model, response, options) => {}, 31 | 32 | // Before creating a value. 33 | // Fired before an `insert` query. 34 | // beforeCreate: async (model, attrs, options) => {}, 35 | 36 | // After creating a value. 37 | // Fired after an `insert` query. 38 | // afterCreate: async (model, attrs, options) => {}, 39 | 40 | // Before updating a value. 41 | // Fired before an `update` query. 42 | // beforeUpdate: async (model, attrs, options) => {}, 43 | 44 | // After updating a value. 45 | // Fired after an `update` query. 46 | // afterUpdate: async (model, attrs, options) => {}, 47 | 48 | // Before destroying a value. 49 | // Fired before a `delete` query. 50 | // beforeDestroy: async (model, attrs, options) => {}, 51 | 52 | // After destroying a value. 53 | // Fired after a `delete` query. 54 | // afterDestroy: async (model, attrs, options) => {} 55 | }; 56 | -------------------------------------------------------------------------------- /client/src/views/Mentor/Classroom/Home/LearningStandardSelect/CheckUnits.jsx: -------------------------------------------------------------------------------- 1 | import {Checkbox} from 'antd'; 2 | import React, {useState} from "react"; 3 | 4 | const CheckboxGroup = Checkbox.Group; 5 | 6 | export default function CheckUnits(props) { 7 | const {plainOptions, checkedList, setCheckedList} = props; 8 | const [indeterminate, setIndeterminate] = useState(false); 9 | const [checkAll, setCheckAll] = useState(true); 10 | 11 | 12 | const onChange = checked => { 13 | setCheckedList(checked.map(value => { 14 | const option = getOptions(value); 15 | return {...option} 16 | })); 17 | setIndeterminate(!!checked.length && checked.length < plainOptions.length); 18 | setCheckAll(checked.length === plainOptions.length) 19 | }; 20 | 21 | const onCheckAllChange = e => { 22 | setCheckedList(e.target.checked ? plainOptions : []); 23 | setIndeterminate(false); 24 | setCheckAll(e.target.checked) 25 | }; 26 | 27 | const getOptions = (value) => { 28 | return plainOptions.find(option => option.number === parseInt(value)) 29 | }; 30 | 31 | return ( 32 | <> 33 |
34 | 39 | All Units 40 | 41 |
42 |
43 | { return { label: `Unit ${option.number}`, value: option.number } })} 45 | value={checkedList.map(checked => checked.number)} 46 | onChange={onChange} 47 | /> 48 | 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /client/src/views/Mentor/Classroom/Classroom.jsx: -------------------------------------------------------------------------------- 1 | import {React, useEffect} from 'react'; 2 | import { Tabs } from 'antd'; 3 | import './Classroom.less'; 4 | 5 | import NavBar from '../../../components/NavBar/NavBar'; 6 | import Roster from './Roster/Roster'; 7 | import Home from './Home/Home'; 8 | import SavedWorkSpaceTab from '../../../components/Tabs/SavedWorkspaceTab'; 9 | import { useSearchParams, useParams } from 'react-router-dom'; 10 | 11 | const { TabPane } = Tabs; 12 | 13 | export default function Classroom({ 14 | handleLogout, 15 | selectedActivity, 16 | setSelectedActivity, 17 | }) { 18 | const [searchParams, setSearchParams] = useSearchParams(); 19 | 20 | const { id } = useParams(); 21 | const tab = searchParams.get('tab'); 22 | const viewing = searchParams.get('viewing'); 23 | 24 | useEffect(() => { 25 | sessionStorage.setItem('classroomId', id); 26 | 27 | }, [id]); 28 | 29 | return ( 30 |
31 | 32 | setSearchParams({ tab: key })} 35 | > 36 | 37 | 43 | 44 | 45 | 46 | 47 | 48 | 53 | 54 | 55 |
56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /server/api/blocks-category/models/blocks-category.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Lifecycle callbacks for the `blocks-category` model. 5 | */ 6 | 7 | module.exports = { 8 | // Before saving a value. 9 | // Fired before an `insert` or `update` query. 10 | // beforeSave: async (model, attrs, options) => {}, 11 | 12 | // After saving a value. 13 | // Fired after an `insert` or `update` query. 14 | // afterSave: async (model, response, options) => {}, 15 | 16 | // Before fetching a value. 17 | // Fired before a `fetch` operation. 18 | // beforeFetch: async (model, columns, options) => {}, 19 | 20 | // After fetching a value. 21 | // Fired after a `fetch` operation. 22 | // afterFetch: async (model, response, options) => {}, 23 | 24 | // Before fetching all values. 25 | // Fired before a `fetchAll` operation. 26 | // beforeFetchAll: async (model, columns, options) => {}, 27 | 28 | // After fetching all values. 29 | // Fired after a `fetchAll` operation. 30 | // afterFetchAll: async (model, response, options) => {}, 31 | 32 | // Before creating a value. 33 | // Fired before an `insert` query. 34 | // beforeCreate: async (model, attrs, options) => {}, 35 | 36 | // After creating a value. 37 | // Fired after an `insert` query. 38 | // afterCreate: async (model, attrs, options) => {}, 39 | 40 | // Before updating a value. 41 | // Fired before an `update` query. 42 | // beforeUpdate: async (model, attrs, options) => {}, 43 | 44 | // After updating a value. 45 | // Fired after an `update` query. 46 | // afterUpdate: async (model, attrs, options) => {}, 47 | 48 | // Before destroying a value. 49 | // Fired before a `delete` query. 50 | // beforeDestroy: async (model, attrs, options) => {}, 51 | 52 | // After destroying a value. 53 | // Fired after a `delete` query. 54 | // afterDestroy: async (model, attrs, options) => {} 55 | }; 56 | -------------------------------------------------------------------------------- /server/api/day/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/days", 6 | "handler": "day.find", 7 | "config": { 8 | "policies": [] 9 | } 10 | }, 11 | { 12 | "method": "GET", 13 | "path": "/days/count", 14 | "handler": "day.count", 15 | "config": { 16 | "policies": [] 17 | } 18 | }, 19 | { 20 | "method": "GET", 21 | "path": "/days/:id", 22 | "handler": "day.findOne", 23 | "config": { 24 | "policies": [] 25 | } 26 | }, 27 | { 28 | "method": "GET", 29 | "path": "/days/toolbox/:id", 30 | "handler": "day.toolbox", 31 | "config": { 32 | "policies": [] 33 | } 34 | }, 35 | { 36 | "method": "PUT", 37 | "path": "/days/arduino/:id", 38 | "handler": "day.arduinoUpdate", 39 | "config": { 40 | "policies": [] 41 | } 42 | }, 43 | { 44 | "method": "POST", 45 | "path": "/days", 46 | "handler": "day.create", 47 | "config": { 48 | "policies": [] 49 | } 50 | }, 51 | { 52 | "method": "PUT", 53 | "path": "/days/template/:id", 54 | "handler": "day.templateUpdate", 55 | "config": { 56 | "policies": [] 57 | } 58 | }, 59 | { 60 | "method": "PUT", 61 | "path": "/days/activity_template/:id", 62 | "handler": "day.activityTemplateUpdate", 63 | "config": { 64 | "policies": [] 65 | } 66 | }, 67 | { 68 | "method": "PUT", 69 | "path": "/days/:id", 70 | "handler": "day.update", 71 | "config": { 72 | "policies": [] 73 | } 74 | }, 75 | { 76 | "method": "DELETE", 77 | "path": "/days/:id", 78 | "handler": "day.delete", 79 | "config": { 80 | "policies": [] 81 | } 82 | } 83 | ] 84 | } 85 | -------------------------------------------------------------------------------- /server/api/unit/controllers/unit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/v3.x/concepts/controllers.html#core-controllers) 5 | * to customize this controller 6 | */ 7 | 8 | module.exports = { 9 | async update(ctx) { 10 | const { id } = ctx.params; 11 | 12 | // ensure request was not sent as formdata 13 | if (ctx.is('multipart')) 14 | return ctx.badRequest('Multipart requests are not accepted!', { 15 | id: 'Unit.update.format.invalid', 16 | error: 'ValidationError', 17 | }); 18 | 19 | // ensure the request has the right number of params 20 | const params = Object.keys(ctx.request.body).length; 21 | if (params !== 5) 22 | return ctx.badRequest('Invalid number of params!', { 23 | id: 'Unit.update.body.invalid', 24 | error: 'ValidationError', 25 | }); 26 | 27 | // validate the request 28 | const { 29 | grade: gradeId, 30 | name, 31 | number, 32 | teks_id, 33 | teks_description, 34 | } = ctx.request.body; 35 | if ( 36 | !strapi.services.validator.isPositiveInt(number) || 37 | !strapi.services.validator.isPositiveInt(gradeId) || 38 | // !teks_id || 39 | !name 40 | // !teks_description 41 | ) 42 | return ctx.badRequest( 43 | 'A grade, name, teks_description must be provided! Number and Teks_id must be positive interger! ', 44 | { id: 'Unit.update.body.invalid', error: 'ValidationError' } 45 | ); 46 | 47 | // ensure the grade is valid 48 | const grade = await strapi.services.grade.findOne({ id: gradeId }); 49 | if (!grade) 50 | return ctx.notFound('The grade provided is invalid!', { 51 | id: 'Unit.update.grade.invalid', 52 | error: 'ValidationError', 53 | }); 54 | 55 | return await strapi.services.unit.update({ id }, ctx.request.body); 56 | }, 57 | }; 58 | -------------------------------------------------------------------------------- /test/functional/contentcreatorMocks.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment node 3 | */ 4 | 'use strict' 5 | 6 | import { getPublicRequestModule, getAuthorizedRequestModule, getMentorLoginData } from '../functional/request' 7 | 8 | const publicRequest = getPublicRequestModule() 9 | var adminRequest 10 | var contentcreatorRequest 11 | 12 | // 13 | // Setup before running tests 14 | // 15 | 16 | beforeAll(async () => { 17 | // login as an admin 18 | const { data: admin } = await publicRequest.post('/admin/login', { 19 | email: 'test@mail.com', 20 | password: '123456' 21 | }) 22 | 23 | // create an admin request instance 24 | adminRequest = getAuthorizedRequestModule(admin.data.token) 25 | }) 26 | 27 | //Tests 28 | test('content creator can login', async () => { 29 | const response = await publicRequest.post('/auth/local', { 30 | identifier: 'defaultcontentcreator', 31 | password: '123456' 32 | }) 33 | 34 | expect(response.status).toBe(200) 35 | expect(response.data).toHaveProperty('jwt') 36 | expect(response.data).toHaveProperty('user') 37 | 38 | contentcreatorRequest = getAuthorizedRequestModule(response.data.jwt) 39 | //console.log("Content Creator Request", response.data.jwt) 40 | }) 41 | 42 | 43 | test('content creator dashboard contains all grades for dropdown of add unit', async () => {//need work on formatting 44 | const response = await contentcreatorRequest.get('/grades'); 45 | 46 | expect(response).toMatchObject({ 47 | data: 48 | [ 49 | { 50 | "id": 1, 51 | "name": "3rd", 52 | }, 53 | { 54 | "id": 3, 55 | "name": "4th", 56 | }, 57 | { 58 | "id": 4, 59 | "name": "5th", 60 | }, 61 | ] 62 | }) 63 | 64 | }) 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /client/src/views/ContentCreator/LearningStandardCreator/LearningStandardCreator.less: -------------------------------------------------------------------------------- 1 | @import '../../../assets/style.less'; 2 | 3 | #class-card { 4 | text-align: left; 5 | width: 35vw; 6 | height: 100%; 7 | margin: 2vw auto 0 auto; 8 | background: #colors[tertiary]; 9 | color: #colors[text-primary]; 10 | border: solid 4px; 11 | border-color: #colors[secondary]; 12 | border-radius: 10px; 13 | } 14 | 15 | #add-learning-standard-btn { 16 | position: relative; 17 | top: -2vh; 18 | width: auto; 19 | max-height: 9vh; 20 | padding: 5px 10px; 21 | font-size: 1em; 22 | font-weight: 500; 23 | border-radius: 30px; 24 | margin: auto; 25 | background: #colors[quaternary]; 26 | border: none; 27 | color: #colors[text-primary]; 28 | transition: 0.25s; 29 | cursor: pointer; 30 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.377); 31 | 32 | &:hover { 33 | background: #colors[quinary]; 34 | } 35 | } 36 | 37 | #add-learning-standard { 38 | input, 39 | textarea { 40 | display: block; 41 | margin: 5px auto 0px; 42 | background: none; 43 | border: 1px solid; 44 | border-color: #colors[secondary]; 45 | padding: 10px 10px; 46 | width: 95%; 47 | outline: none; 48 | color: #colors[text-primary]; 49 | border-radius: 4px; 50 | transition: 0.25s; 51 | } 52 | 53 | input[type='text']:focus { 54 | // width: 100%; 55 | } 56 | 57 | label { 58 | margin: 10px auto 12px; 59 | white-space: pre-wrap; 60 | } 61 | } 62 | 63 | #unit-name-dropdown { 64 | display: block; 65 | background: none; 66 | margin: 5px auto 0px; 67 | border: 1px solid; 68 | border-color: #colors[secondary]; 69 | padding: 14px 10px; 70 | width: 95%; 71 | outline: none; 72 | color: #colors[text-primary]; 73 | border-radius: 4px; 74 | transition: 0.25s; 75 | } 76 | 77 | #unit:focus { 78 | width: 100%; 79 | } 80 | 81 | #link-Input { 82 | white-space: pre-wrap; 83 | margin-bottom: 20px; 84 | } 85 | -------------------------------------------------------------------------------- /server/api/student/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/students", 6 | "handler": "student.find", 7 | "config": { 8 | "policies": [] 9 | } 10 | }, 11 | { 12 | "method": "GET", 13 | "path": "/students/me", 14 | "handler": "student.me", 15 | "config": { 16 | "policies": ["global::isStudent"] 17 | } 18 | }, 19 | { 20 | "method": "GET", 21 | "path": "/students/count", 22 | "handler": "student.count", 23 | "config": { 24 | "policies": [] 25 | } 26 | }, 27 | { 28 | "method": "GET", 29 | "path": "/students/:id", 30 | "handler": "student.findOne", 31 | "config": { 32 | "policies": [] 33 | } 34 | }, 35 | { 36 | "method": "POST", 37 | "path": "/students", 38 | "handler": "student.create", 39 | "config": { 40 | "policies": ["global::isClassroomManager", "global::hasClassroom"] 41 | } 42 | }, 43 | { 44 | "method": "PUT", 45 | "path": "/students/:id", 46 | "handler": "student.update", 47 | "config": { 48 | "policies": ["global::isClassroomManager", "global::hasStudentsClassroom"] 49 | } 50 | }, 51 | { 52 | "method": "PUT", 53 | "path": "/students/:id/addvideo", 54 | "handler": "student.addVideo", 55 | "config": { 56 | "policies" : [] 57 | } 58 | }, 59 | { 60 | "method": "DELETE", 61 | "path": "/students/:id", 62 | "handler": "student.delete", 63 | "config": { 64 | "policies": ["global::isClassroomManager", "global::hasStudentsClassroom"] 65 | } 66 | }, 67 | { 68 | "method": "PUT", 69 | "path": "/students/enrolled/:id", 70 | "handler": "student.enrolled", 71 | "config": { 72 | "policies": ["global::isClassroomManager", "global::hasStudentsClassroom"] 73 | } 74 | } 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /client/src/views/Student/Student.less: -------------------------------------------------------------------------------- 1 | @import "../../assets/style"; 2 | 3 | #activity-container { 4 | position: absolute; 5 | top: 53vh; 6 | left: 10vw; 7 | margin: -40vh auto 0 auto; 8 | height: 85vh; 9 | width: 80vw; 10 | background-color: #colors[tertiary]; 11 | border-radius: 15px; 12 | border: 4px solid; 13 | border-color: #colors[secondary]; 14 | } 15 | 16 | #header { 17 | background-color: #colors[secondary]; 18 | border-radius: 80px; 19 | width: 40%; 20 | min-height: 4vh; 21 | color: #colors[text-secondary]; 22 | font-size: 2.25vh; 23 | font-weight: bold; 24 | position: relative; 25 | top: -20px; 26 | left: -30px; 27 | line-height: 45px; 28 | text-align: center; 29 | } 30 | 31 | #list-item-wrapper { 32 | display: flex; 33 | justify-content: center; 34 | align-items: center; 35 | background-color: #colors[quaternary]; 36 | border: none; 37 | border-radius: 10px; 38 | margin: 0 auto 1.2em auto; 39 | width: 80%; 40 | height: 40px; 41 | font-size: 2.5vh; 42 | font-weight: 500; 43 | color: #colors[text-primary]; 44 | line-height: 30px; 45 | transition: 0.25s; 46 | &:hover { 47 | cursor: pointer; 48 | background-color: #colors[quinary]; 49 | width: 82%; 50 | } 51 | } 52 | 53 | ul { 54 | margin: 0; 55 | padding: 0; 56 | } 57 | 58 | li { 59 | list-style-type: none; 60 | margin: 0; 61 | padding: 0; 62 | } 63 | 64 | #launcher { 65 | position: fixed; 66 | bottom: 3vh; 67 | right: 11vw; 68 | color: #colors[primary]; 69 | background-color: darken(#colors[tertiary], 5%); 70 | font-weight: 550; 71 | width: 10vw; 72 | font-size: 2.75vh; 73 | border: solid 3px; 74 | border-radius: 10px; 75 | border-color: #colors[primary]; 76 | i { 77 | margin-top: 5px; 78 | font-size: 8vh; 79 | } 80 | transition: 0.25s; 81 | &:hover { 82 | cursor: pointer; 83 | color: #colors[secondary]; 84 | border-color: #colors[secondary]; 85 | i { 86 | color: #colors[secondary]; 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /server/api/block/services/block.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const sortCategory = (toolbox) => { 4 | const order = [ 5 | 'Logic', 6 | 'Control', 7 | 'Math', 8 | 'Text', 9 | 'Variables', 10 | 'Functions', 11 | 'IO', 12 | 'Time', 13 | 'Audio', 14 | 'Motors', 15 | 'Comms', 16 | 'DHT', 17 | ]; 18 | 19 | const sorted = toolbox.sort(function (a, b) { 20 | return order.indexOf(a[0]) - order.indexOf(b[0]); 21 | }); 22 | return sorted; 23 | }; 24 | 25 | // get all the blocks for a day 26 | module.exports.findByDay = async (id) => { 27 | // get the target day 28 | const day = await strapi.services.day.findOne({ id }, [ 29 | 'blocks.blocks_category', 30 | ]); 31 | 32 | // return the blocks only if the day is found 33 | return day ? day.blocks : undefined; 34 | }; 35 | 36 | // get all the blocks for a cc workspace 37 | module.exports.findByWorkspace = async (id) => { 38 | // get the target day 39 | const workspace = await strapi.services['cc-workspace'].findOne({ id }, [ 40 | 'blocks.blocks_category', 41 | ]); 42 | 43 | // return the blocks only if the day is found 44 | return workspace ? workspace.blocks : undefined; 45 | }; 46 | 47 | // create a blockly friendly toolbox from blocks 48 | module.exports.blocksToToolbox = (blocks) => { 49 | let toolbox = {}; 50 | blocks.forEach((block) => { 51 | // validate the block fields 52 | const { blocks_category, name, description, image_url } = block; 53 | if (!blocks_category) return; 54 | 55 | // only take the required fields from the block 56 | let sanitizedBlock = { name, description, image_url }; 57 | 58 | // append the block to an existing category 59 | // else create a new category 60 | if (toolbox[blocks_category.name]) { 61 | toolbox[blocks_category.name].push(sanitizedBlock); 62 | } else { 63 | toolbox[blocks_category.name] = [sanitizedBlock]; 64 | } 65 | }); 66 | const arr = Object.entries(toolbox); 67 | 68 | return sortCategory(arr); 69 | }; 70 | --------------------------------------------------------------------------------