mirror of
https://github.com/Leado123/sharesyllabus-server.git
synced 2026-10-01 00:13:00 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
* text=auto
|
||||||
+175
@@ -0,0 +1,175 @@
|
|||||||
|
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
|
||||||
|
logs
|
||||||
|
_.log
|
||||||
|
npm-debug.log_
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Caches
|
||||||
|
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
|
||||||
|
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
|
||||||
|
pids
|
||||||
|
_.pid
|
||||||
|
_.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
|
||||||
|
.temp
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
# IntelliJ based IDEs
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Finder (MacOS) folder config
|
||||||
|
.DS_Store
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"schema": "path/to/schema.graphql",
|
||||||
|
"documents": "src/**/*.{graphql,ts,tsx}"
|
||||||
|
}
|
||||||
+225
@@ -0,0 +1,225 @@
|
|||||||
|
ENG-1A
|
||||||
|
ENG-1AH
|
||||||
|
ALR-4
|
||||||
|
ENG-1B
|
||||||
|
ENG-1BH
|
||||||
|
PHI-11
|
||||||
|
COM-1
|
||||||
|
COM-1H
|
||||||
|
COM-6
|
||||||
|
COM-9
|
||||||
|
COM-9H
|
||||||
|
MAT-1A
|
||||||
|
MAT-5
|
||||||
|
MAT-25
|
||||||
|
MAT-1AH
|
||||||
|
MAT-9
|
||||||
|
MAT-70A
|
||||||
|
MAT-1B
|
||||||
|
MAT-10
|
||||||
|
MAT-70B
|
||||||
|
MAT-1C
|
||||||
|
MAT-11
|
||||||
|
PSY-48
|
||||||
|
SOC-48
|
||||||
|
MAT-2
|
||||||
|
MAT-12
|
||||||
|
MAT-12H
|
||||||
|
MAT-3
|
||||||
|
AHS-1
|
||||||
|
AHS-9
|
||||||
|
GAM-2
|
||||||
|
AHS-10
|
||||||
|
MUS-3
|
||||||
|
MUS-19
|
||||||
|
MUS-19H
|
||||||
|
AHS-2
|
||||||
|
AHS-2H
|
||||||
|
AHS-12
|
||||||
|
AHS-13
|
||||||
|
AHS-14
|
||||||
|
ARE-35
|
||||||
|
ARE-36
|
||||||
|
AHS-4
|
||||||
|
AHS-5
|
||||||
|
AHS-6
|
||||||
|
ART-6
|
||||||
|
AHS-6H
|
||||||
|
ART-6H
|
||||||
|
AHS-7
|
||||||
|
AHS-8
|
||||||
|
DAN-6
|
||||||
|
DAN-6H
|
||||||
|
MUS-23
|
||||||
|
MUS-25
|
||||||
|
MUS-89
|
||||||
|
MUS-89H
|
||||||
|
THE-3
|
||||||
|
THE-9
|
||||||
|
THE-29
|
||||||
|
HUM-4
|
||||||
|
HUM-4H
|
||||||
|
HUM-5
|
||||||
|
HUM-5H
|
||||||
|
HUM-8
|
||||||
|
ENG-8
|
||||||
|
HUM-9
|
||||||
|
HUM-10
|
||||||
|
HUM-10H
|
||||||
|
HUM-11
|
||||||
|
HUM-16
|
||||||
|
HUM-18
|
||||||
|
PHI-18
|
||||||
|
HUM-23
|
||||||
|
ENG-23
|
||||||
|
HUM-35
|
||||||
|
PHI-35
|
||||||
|
PHI-36
|
||||||
|
SPA-2
|
||||||
|
SPA-3
|
||||||
|
SPA-4
|
||||||
|
SPA-8
|
||||||
|
SPA-11
|
||||||
|
SPA-12
|
||||||
|
CHI-11
|
||||||
|
COM-12
|
||||||
|
ETS-2
|
||||||
|
ETS-7
|
||||||
|
ETS-8
|
||||||
|
ETS-14
|
||||||
|
FRE-2
|
||||||
|
FRE-8
|
||||||
|
HIS-1
|
||||||
|
HIS-2
|
||||||
|
HIS-6
|
||||||
|
HIS-6H
|
||||||
|
HIS-7
|
||||||
|
HIS-7H
|
||||||
|
HIS-14
|
||||||
|
HIS-25
|
||||||
|
HIS-26
|
||||||
|
HIS-31
|
||||||
|
HIS-34
|
||||||
|
PHI-10
|
||||||
|
PHI-10H
|
||||||
|
PHI-12
|
||||||
|
PHI-19
|
||||||
|
PHI-22
|
||||||
|
PHI-33
|
||||||
|
PHI-34
|
||||||
|
ADJ-1
|
||||||
|
ADJ-3
|
||||||
|
ADJ-9
|
||||||
|
ADJ-9H
|
||||||
|
ANT-1
|
||||||
|
ANT-1H
|
||||||
|
ANT-2
|
||||||
|
ANT-2H
|
||||||
|
ANT-3
|
||||||
|
ANT-4
|
||||||
|
ANT-5
|
||||||
|
ANT-6
|
||||||
|
ANT-7
|
||||||
|
ANT-8
|
||||||
|
COM-13
|
||||||
|
COM-20
|
||||||
|
EAR-20
|
||||||
|
ECO-4
|
||||||
|
ECO-7
|
||||||
|
ECO-7H
|
||||||
|
ECO-8
|
||||||
|
ECO-8H
|
||||||
|
ETS-1
|
||||||
|
ETS-3
|
||||||
|
ETS-21
|
||||||
|
ETS-23
|
||||||
|
ETS-27
|
||||||
|
GEG-2
|
||||||
|
GEG-3
|
||||||
|
GEG-4
|
||||||
|
GEG-6
|
||||||
|
HIS-34
|
||||||
|
JOU-7
|
||||||
|
POL-1
|
||||||
|
POL-1H
|
||||||
|
POL-2
|
||||||
|
POL-4
|
||||||
|
POL-4H
|
||||||
|
POL-5
|
||||||
|
POL-11
|
||||||
|
POL-13
|
||||||
|
POL-20
|
||||||
|
POL-21
|
||||||
|
POL-22
|
||||||
|
POL-23
|
||||||
|
PSY-1
|
||||||
|
PSY-1H
|
||||||
|
PSY-2
|
||||||
|
PSY-8
|
||||||
|
PSY-9
|
||||||
|
PSY-11
|
||||||
|
PSY-33
|
||||||
|
PSY-35
|
||||||
|
PSY-35H
|
||||||
|
PSY-50
|
||||||
|
SJS-110
|
||||||
|
SOC-1
|
||||||
|
SOC-1H
|
||||||
|
SOC-2
|
||||||
|
SOC-3
|
||||||
|
SOC-10
|
||||||
|
SOC-10H
|
||||||
|
SOC-12
|
||||||
|
SOC-15
|
||||||
|
SOC-20
|
||||||
|
SOC-50
|
||||||
|
CHE-1A
|
||||||
|
CHE-1B
|
||||||
|
CHE-2A
|
||||||
|
CHE-2B
|
||||||
|
CHE-3
|
||||||
|
CHE-10
|
||||||
|
CHE-12A
|
||||||
|
CHE-12B
|
||||||
|
GEG-1
|
||||||
|
GEG-1H
|
||||||
|
GEG-1L
|
||||||
|
GEG-5
|
||||||
|
PHS-1
|
||||||
|
PHY-2A
|
||||||
|
PHY-2B
|
||||||
|
PHY-4A
|
||||||
|
PHY-4B
|
||||||
|
PHY-4C
|
||||||
|
PHY-10
|
||||||
|
PHY-11
|
||||||
|
ANT-1L
|
||||||
|
BIO-1
|
||||||
|
BIO-1H
|
||||||
|
BIO-3
|
||||||
|
BIO-4
|
||||||
|
BIO-5
|
||||||
|
BIO-7
|
||||||
|
BIO-8
|
||||||
|
BIO-10
|
||||||
|
BIO-16
|
||||||
|
BIO-18
|
||||||
|
BIO-19
|
||||||
|
BIO-21
|
||||||
|
BIO-45
|
||||||
|
BIO-50A
|
||||||
|
BIO-50B
|
||||||
|
BIO-55
|
||||||
|
BIO-60
|
||||||
|
BIO-61
|
||||||
|
CHI-2
|
||||||
|
ARA-1
|
||||||
|
ASL-1
|
||||||
|
FRE-1
|
||||||
|
SPA-1
|
||||||
|
ETS-1
|
||||||
|
ETS-3
|
||||||
|
ETS-7
|
||||||
|
ETS-8
|
||||||
|
ETS-21
|
||||||
|
ETS-23
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
ENG-1A: English Composition
|
||||||
|
ENG-1AH: Honors English Composition
|
||||||
|
ALR-4: Intermediate Algebra
|
||||||
|
ENG-1B: Critical Thinking and Writing
|
||||||
|
ENG-1BH: Honors Critical Thinking and Writing
|
||||||
|
PHI-11: Critical Thinking
|
||||||
|
COM-1: Public Speaking
|
||||||
|
COM-1H: Honors Public Speaking
|
||||||
|
COM-6: Dynamics of Small Group Communication
|
||||||
|
COM-9: Interpersonal Communication
|
||||||
|
COM-9H: Honors Interpersonal Communication
|
||||||
|
MAT-1A: Calculus I
|
||||||
|
MAT-5: Discrete Structures
|
||||||
|
MAT-25: Mathematics for the Liberal Arts Student
|
||||||
|
MAT-1AH: Honors Calculus I
|
||||||
|
MAT-9: Introduction to Statistics
|
||||||
|
MAT-70A: Intermediate Algebra Part I
|
||||||
|
MAT-1B: Calculus II
|
||||||
|
MAT-10: Precalculus
|
||||||
|
MAT-70B: Intermediate Algebra Part II
|
||||||
|
MAT-1C: Calculus III
|
||||||
|
MAT-11: College Algebra
|
||||||
|
PSY-48: Introduction to Chemical Dependency
|
||||||
|
SOC-48: Introduction to Chemical Dependency
|
||||||
|
MAT-2: Differential Equations
|
||||||
|
MAT-12: Statistics
|
||||||
|
MAT-12H: Honors Statistics
|
||||||
|
MAT-3: Linear Algebra
|
||||||
|
AHS-1: Introduction to Allied Health
|
||||||
|
AHS-9: Medical Terminology
|
||||||
|
GAM-2: Introduction to Game Development
|
||||||
|
AHS-10: Health Education
|
||||||
|
MUS-3: Introduction to Music
|
||||||
|
MUS-19: Introduction to Music Theory
|
||||||
|
MUS-19H: Honors Introduction to Music Theory
|
||||||
|
AHS-2: Medical Terminology
|
||||||
|
AHS-2H: Honors Medical Terminology
|
||||||
|
AHS-12: Healthcare Ethics
|
||||||
|
AHS-13: Pharmacology
|
||||||
|
AHS-14: Clinical Practices in Allied Health
|
||||||
|
ARE-35: Agricultural Economics
|
||||||
|
ARE-36: Agricultural Business
|
||||||
|
AHS-4: Patient Care Procedures
|
||||||
|
AHS-5: Basic Medical Office Procedures
|
||||||
|
AHS-6: Introduction to Healthcare
|
||||||
|
ART-6: Art Appreciation
|
||||||
|
AHS-6H: Honors Introduction to Healthcare
|
||||||
|
ART-6H: Honors Art Appreciation
|
||||||
|
AHS-7: Anatomy and Physiology
|
||||||
|
AHS-8: Medical Assistant Skills
|
||||||
|
DAN-6: Dance Appreciation
|
||||||
|
DAN-6H: Honors Dance Appreciation
|
||||||
|
MUS-23: History of Jazz
|
||||||
|
MUS-25: History of Rock and Roll
|
||||||
|
MUS-89: Music Production
|
||||||
|
MUS-89H: Honors Music Production
|
||||||
|
THE-3: Introduction to Theatre
|
||||||
|
THE-9: History of Theatre
|
||||||
|
THE-29: Technical Theatre
|
||||||
|
HUM-4: Introduction to Humanities
|
||||||
|
HUM-4H: Honors Introduction to Humanities
|
||||||
|
HUM-5: World Religions
|
||||||
|
HUM-5H: Honors World Religions
|
||||||
|
HUM-8: Introduction to Philosophy
|
||||||
|
ENG-8: Introduction to Mythology
|
||||||
|
HUM-9: Introduction to Ethics
|
||||||
|
HUM-10: World Civilizations
|
||||||
|
HUM-10H: Honors World Civilizations
|
||||||
|
HUM-11: History of Art
|
||||||
|
HUM-16: Humanities of Latin America
|
||||||
|
HUM-18: Humanities of Asia
|
||||||
|
PHI-18: Philosophy of Religion
|
||||||
|
HUM-23: Women in Western Culture
|
||||||
|
ENG-23: The Bible as Literature
|
||||||
|
HUM-35: Cultural History of the United States
|
||||||
|
PHI-35: Introduction to Ethics
|
||||||
|
PHI-36: Asian Philosophy
|
||||||
|
SPA-2: Spanish Language and Culture
|
||||||
|
SPA-3: Intermediate Spanish
|
||||||
|
SPA-4: Advanced Spanish
|
||||||
|
SPA-8: Spanish for Native Speakers
|
||||||
|
SPA-11: Spanish Conversation
|
||||||
|
SPA-12: Spanish Composition
|
||||||
|
CHI-11: Chinese Language and Culture
|
||||||
|
COM-12: Intercultural Communication
|
||||||
|
ETS-2: Electronic Tools for Students
|
||||||
|
ETS-7: Computer Applications for Business
|
||||||
|
ETS-8: Introduction to Digital Media
|
||||||
|
ETS-14: Web Design and Development
|
||||||
|
FRE-2: French Language and Culture
|
||||||
|
FRE-8: French Literature
|
||||||
|
HIS-1: World History
|
||||||
|
HIS-2: United States History
|
||||||
|
HIS-6: Introduction to Historical Research
|
||||||
|
HIS-6H: Honors Introduction to Historical Research
|
||||||
|
HIS-7: History of Western Civilization
|
||||||
|
HIS-7H: Honors History of Western Civilization
|
||||||
|
HIS-14: History of California
|
||||||
|
HIS-25: History of the United States
|
||||||
|
HIS-26: History of the American West
|
||||||
|
HIS-31: History of Mexico
|
||||||
|
HIS-34: History of Latin America
|
||||||
|
PHI-10: Introduction to Philosophy
|
||||||
|
PHI-10H: Honors Introduction to Philosophy
|
||||||
|
PHI-12: Introduction to Ethics
|
||||||
|
PHI-19: Native American Thought
|
||||||
|
PHI-22: Philosophy of Science
|
||||||
|
PHI-33: Introduction to Social and Political Philosophy
|
||||||
|
PHI-34: Philosophy of Art and Beauty
|
||||||
|
ADJ-1: Introduction to the Administration of Justice
|
||||||
|
ADJ-3: Concepts of Criminal Law
|
||||||
|
ADJ-9: Law in American Society
|
||||||
|
ADJ-9H: Honors Law in American Society
|
||||||
|
ANT-1: Introduction to Anthropology
|
||||||
|
ANT-1H: Honors Introduction to Anthropology
|
||||||
|
ANT-2: Cultural Anthropology
|
||||||
|
ANT-2H: Honors Cultural Anthropology
|
||||||
|
ANT-3: Archaeology
|
||||||
|
ANT-4: Human Evolution
|
||||||
|
ANT-5: Physical Anthropology
|
||||||
|
ANT-6: Native American Studies
|
||||||
|
ANT-7: Anthropology of Religion
|
||||||
|
ANT-8: Human Behavior and Society
|
||||||
|
COM-13: Gender and Communication
|
||||||
|
COM-20: Introduction to Communication Theory
|
||||||
|
EAR-20: Introduction to Environmental Science
|
||||||
|
ECO-4: Microeconomics
|
||||||
|
ECO-7: Macroeconomics
|
||||||
|
ECO-7H: Honors Macroeconomics
|
||||||
|
ECO-8: Principles of Economics
|
||||||
|
ECO-8H: Honors Principles of Economics
|
||||||
|
ETS-1: Introduction to Information Systems
|
||||||
|
ETS-3: Information Technology
|
||||||
|
ETS-21: Computer Science
|
||||||
|
ETS-23: Web Development
|
||||||
|
GEG-2: World Geography
|
||||||
|
GEG-3: Geography of the United States
|
||||||
|
GEG-4: Geography of California
|
||||||
|
GEG-6: Economic Geography
|
||||||
|
HIS-34: History of Latin America
|
||||||
|
JOU-7: Introduction to Journalism
|
||||||
|
POL-1: Introduction to Political Science
|
||||||
|
POL-1H: Honors Introduction to Political Science
|
||||||
|
POL-2: Comparative Politics
|
||||||
|
POL-4: International Relations
|
||||||
|
POL-4H: Honors International Relations
|
||||||
|
POL-5: United States Government
|
||||||
|
POL-11: Political Theory
|
||||||
|
POL-13: Political Ideologies
|
||||||
|
POL-20: Introduction to Political Economy
|
||||||
|
POL-21: California Politics
|
||||||
|
POL-22: Environmental Politics
|
||||||
|
POL-23: Politics of the Middle East
|
||||||
|
PSY-1: General Psychology
|
||||||
|
PSY-1H: Honors General Psychology
|
||||||
|
PSY-2: Biological Psychology
|
||||||
|
PSY-8: Introduction to Social Psychology
|
||||||
|
PSY-9: Developmental Psychology
|
||||||
|
PSY-11: Psychology of Gender
|
||||||
|
PSY-33: Theories of Personality
|
||||||
|
PSY-35: Abnormal Psychology
|
||||||
|
PSY-35H: Honors Abnormal Psychology
|
||||||
|
PSY-50: Research Methods in Psychology
|
||||||
|
SJS-110: Introduction to Social Justice
|
||||||
|
SOC-1: Introduction to Sociology
|
||||||
|
SOC-1H: Honors Introduction to Sociology
|
||||||
|
SOC-2: American Social Problems
|
||||||
|
SOC-3: Marriage and Family Relations
|
||||||
|
SOC-10: Race and Ethnic Relations
|
||||||
|
SOC-10H: Honors Race and Ethnic Relations
|
||||||
|
SOC-12: Introduction to Gender
|
||||||
|
SOC-15: Sociology of Health and Illness
|
||||||
|
SOC-20: Criminology
|
||||||
|
SOC-50: Social Research Methods
|
||||||
|
CHE-1A: General Chemistry 1
|
||||||
|
CHE-1B: General Chemistry 2
|
||||||
|
CHE-2A: Organic Chemistry 1
|
||||||
|
CHE-2B: Organic Chemistry 2
|
||||||
|
CHE-3: Introduction to Chemistry
|
||||||
|
CHE-10: Environmental Chemistry
|
||||||
|
CHE-12A: Biochemistry
|
||||||
|
CHE-12B: Biochemistry
|
||||||
|
GEG-1: Introduction to Geography
|
||||||
|
GEG-1H: Honors Introduction to Geography
|
||||||
|
GEG-1L: Geography Laboratory
|
||||||
|
GEG-5: Geography of California
|
||||||
|
PHS-1: Physical Science
|
||||||
|
PHY-2A: General Physics
|
||||||
|
PHY-2B: General Physics
|
||||||
|
PHY-4A: Physics for Scientists and Engineers
|
||||||
|
PHY-4B: Physics for Scientists and Engineers
|
||||||
|
PHY-4C: Physics for Scientists and Engineers
|
||||||
|
PHY-10: Introduction to Physics
|
||||||
|
PHY-11: Principles of Physics
|
||||||
|
ANT-1L: Introduction to Physical Anthropology Lab
|
||||||
|
BIO-1: General Biology
|
||||||
|
BIO-1H: Honors General Biology
|
||||||
|
BIO-3: Introduction to Microbiology
|
||||||
|
BIO-4: Introduction to Environmental Science
|
||||||
|
BIO-5: Human Anatomy
|
||||||
|
BIO-7: Human Physiology
|
||||||
|
BIO-8: Human Biology
|
||||||
|
BIO-10: General Botany
|
||||||
|
BIO-16: Ecology
|
||||||
|
BIO-18: Marine Biology
|
||||||
|
BIO-19: Introduction to Biology
|
||||||
|
BIO-21: General Genetics
|
||||||
|
BIO-45: Human Genetics
|
||||||
|
BIO-50A: Principles of Biology
|
||||||
|
BIO-50B: Principles of Biology
|
||||||
|
BIO-55: Botany
|
||||||
|
BIO-60: Molecular Biology
|
||||||
|
BIO-61: Microbiology
|
||||||
|
CHI-2: Chinese Language and Culture
|
||||||
|
ARA-1: Arabic Language and Culture
|
||||||
|
ASL-1: American Sign Language
|
||||||
|
FRE-1: French Language and Culture
|
||||||
|
SPA-1: Introduction to Spanish Language
|
||||||
|
ETS-1: Introduction to Information Systems
|
||||||
|
ETS-3: Information Technology
|
||||||
|
ETS-7: Computer Applications for Business
|
||||||
|
ETS-8: Introduction to Digital Media
|
||||||
|
ETS-21: Computer Science
|
||||||
|
ETS-23: Web Development
|
||||||
@@ -0,0 +1,360 @@
|
|||||||
|
import { PrismaClient, type Syllabus } from "@prisma/client";
|
||||||
|
import express from "express";
|
||||||
|
import cors from "cors";
|
||||||
|
import fileUpload from "express-fileupload";
|
||||||
|
import http from "node:http";
|
||||||
|
import fuzzysort from "fuzzysort";
|
||||||
|
import mime from "mime-types";
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import argon2 from 'argon2';
|
||||||
|
import { idText } from "typescript";
|
||||||
|
import 'dotenv/config';
|
||||||
|
import { jwtMiddleware } from "./authMiddleware";
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const httpServer = http.createServer(app);
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
app.use(cors({
|
||||||
|
origin: "*", // TODO change this to the actual domain
|
||||||
|
}))
|
||||||
|
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
app.use(fileUpload());
|
||||||
|
|
||||||
|
app.use('/files', express.static('syllabi'));
|
||||||
|
|
||||||
|
app.post("/create", async (req, res) => {
|
||||||
|
console.debug(req.body);
|
||||||
|
if (req.files === null || req.files === undefined)
|
||||||
|
return res.status(400).json({ msg: "No file uploaded" });
|
||||||
|
if (req.files.upload === null)
|
||||||
|
return res.status(400).json({ msg: "No file uploaded" });
|
||||||
|
if (Array.isArray(req.files.upload))
|
||||||
|
return res.status(400).json({ msg: "Multiple files uploaded" });
|
||||||
|
const file = req.files.upload as fileUpload.UploadedFile;
|
||||||
|
|
||||||
|
if (![mime.lookup(".pdf"), mime.lookup(".docx")].includes(file.mimetype)) return res.status(400).json({ msg: "Invalid file type" });
|
||||||
|
if (file.size > 5_000_000) {
|
||||||
|
return res.status(400).json({ msg: "File too large (max 5MB)" });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Add error handling for missing required fields
|
||||||
|
if (!req.body.schoolName || !req.body.className || !req.body.professor) {
|
||||||
|
return res.status(400).json({ msg: "Missing required fields" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add proper error handling for file operations
|
||||||
|
try {
|
||||||
|
let school = await prisma.school.findUnique({
|
||||||
|
where: { name: req.body.schoolName },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!school) {
|
||||||
|
return res.status(404).json({ msg: "School not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
let classRecord = await prisma.class.findFirst({ // Change findUnique to findFirst
|
||||||
|
where: {
|
||||||
|
className: req.body.className,
|
||||||
|
schoolId: school.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!classRecord) {
|
||||||
|
return res.status(404).json({ msg: "Class not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
let professorObject = await prisma.professor.findUnique({
|
||||||
|
where: { name: req.body.professor },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!professorObject) {
|
||||||
|
professorObject = await prisma.professor.create({
|
||||||
|
data: {
|
||||||
|
name: req.body.professor,
|
||||||
|
schoolId: school.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let { id } = await prisma.syllabus.create({
|
||||||
|
data: {
|
||||||
|
mimeType: file.mimetype,
|
||||||
|
createdByName: req.body.name || undefined,
|
||||||
|
createdByEmail: req.body.email || undefined,
|
||||||
|
fullClassName: req.body.fullClassName || undefined,
|
||||||
|
classLength: req.body.classLength ? parseInt(req.body.classLength, 10) : undefined,
|
||||||
|
textbookCost: req.body.textbookCost || undefined,
|
||||||
|
description: req.body.description || undefined,
|
||||||
|
content: "", // Add the content property
|
||||||
|
class: { connect: { id: classRecord.id } },
|
||||||
|
professorObject: { connect: { id: professorObject.id } },
|
||||||
|
school: { connect: { id: school.id } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await file.mv(`./syllabi/${id}.${mime.extension(file.mimetype)}`);
|
||||||
|
return res.status(200).json({ id });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating syllabus:', error);
|
||||||
|
if (error instanceof Error) {
|
||||||
|
return res.status(500).json({ msg: "Internal server error", error: error.message });
|
||||||
|
}
|
||||||
|
return res.status(500).json({ msg: "Internal server error" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/schools", async (req, res) => {
|
||||||
|
let colleges = await prisma.school.findMany({});
|
||||||
|
res.json(colleges);
|
||||||
|
})
|
||||||
|
|
||||||
|
app.post("/search/class/", async (req, res) => {
|
||||||
|
let take = req.body.take || 10;
|
||||||
|
let skip = req.body.skip || 0;
|
||||||
|
|
||||||
|
if (await prisma.school.findUnique({ where: { name: req.query.s as string } }) === null) return res.status(404).json({ msg: "School not found" });
|
||||||
|
|
||||||
|
let classes = await prisma.class.findMany({
|
||||||
|
where: {
|
||||||
|
school: {
|
||||||
|
name: req.query.s as string,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
let results = classes;
|
||||||
|
|
||||||
|
if (req.query.q) {
|
||||||
|
results = fuzzysort.go(req.query.q as string, classes, { keys: ["className", "fullClassName"] }).map(i => i.obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
res.json(results.slice(skip, skip + take));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/search/professor/", async (req, res) => {
|
||||||
|
let take = req.body.take || 10;
|
||||||
|
let skip = req.body.skip || 0;
|
||||||
|
|
||||||
|
if (await prisma.school.findUnique({ where: { name: req.query.s as string } }) === null) return res.status(404).json({ msg: "School not found" });
|
||||||
|
|
||||||
|
let professors = await prisma.professor.findMany({
|
||||||
|
where: {
|
||||||
|
school: {
|
||||||
|
name: req.query.s as string,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let results = professors;
|
||||||
|
|
||||||
|
if (req.query.q) {
|
||||||
|
results = fuzzysort.go(req.query.q as string, professors, { keys: ["name"] }).map(i => i.obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(results.slice(skip, skip + take));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/professor/:id", async (req, res) => {
|
||||||
|
let professor = await prisma.professor.findUnique({
|
||||||
|
where: { id: req.params.id },
|
||||||
|
include: {
|
||||||
|
school: true,
|
||||||
|
syllabi: {
|
||||||
|
include: {
|
||||||
|
class: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (professor === null) return res.status(404).json({ msg: "Professor not found" });
|
||||||
|
return res.json({
|
||||||
|
name: professor.name,
|
||||||
|
school: professor.school?.name,
|
||||||
|
syllabi: professor.syllabi.map(i => {
|
||||||
|
return {
|
||||||
|
id: i.id,
|
||||||
|
className: i.class?.className,
|
||||||
|
fullClassName: i.fullClassName,
|
||||||
|
textbookCost: i.textbookCost,
|
||||||
|
classLength: i.classLength,
|
||||||
|
description: i.description,
|
||||||
|
dateCreated: i.dateCreated.toISOString(),
|
||||||
|
fileName: `${i.id}.${mime.extension(i.mimeType)}`,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/syllabus/:id", async (req, res) => {
|
||||||
|
let syllabus = await prisma.syllabus.findUnique({
|
||||||
|
where: { id: req.params.id},
|
||||||
|
include: {
|
||||||
|
class: {
|
||||||
|
include: {
|
||||||
|
discipline: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
professorObject: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (syllabus === null) return res.status(404).json({ msg: "Syllabus not found" });
|
||||||
|
return res.json({
|
||||||
|
className: syllabus.class?.className,
|
||||||
|
description: syllabus.description,
|
||||||
|
fullClassName: syllabus.fullClassName,
|
||||||
|
textbookCost: syllabus.textbookCost,
|
||||||
|
content: syllabus.content,
|
||||||
|
classLength: syllabus.classLength,
|
||||||
|
professor: syllabus.professorObject?.name,
|
||||||
|
dateCreated: syllabus.dateCreated.toISOString(),
|
||||||
|
fileName: `${syllabus.id}.${mime.extension(syllabus.mimeType)}`,
|
||||||
|
class: {
|
||||||
|
className: syllabus.class?.className ?? '',
|
||||||
|
fullClassName: syllabus.class?.fullClassName ?? '',
|
||||||
|
discipline: syllabus.class?.discipline?.name ?? '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/syllabus/:id/download", async (req, res) => {
|
||||||
|
let syllabus = await prisma.syllabus.findUnique({
|
||||||
|
where: { id: req.params.id, reviewed: true },
|
||||||
|
include: {
|
||||||
|
class: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (syllabus === null) return res.status(404).json({ msg: "Syllabus not found" });
|
||||||
|
res.download(`./syllabi/${syllabus.id}.${mime.extension(syllabus.mimeType)}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/search/", async (req, res) => {
|
||||||
|
try {
|
||||||
|
let syllabi = await prisma.syllabus.findMany({
|
||||||
|
include: {
|
||||||
|
class: {
|
||||||
|
include: {
|
||||||
|
discipline: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
professorObject: true,
|
||||||
|
school: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let results = syllabi;
|
||||||
|
|
||||||
|
if (req.query.s) {
|
||||||
|
results = results.filter(i => i.school?.name === req.query.s);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.query.c) {
|
||||||
|
results = results.filter(i => i.class?.className === req.query.c);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.query.p) {
|
||||||
|
results = results.filter(i => i.professorObject?.name === req.query.p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.query.q) {
|
||||||
|
results = fuzzysort.go(req.query.q as string, results, { keys: ["class.className", "professorObject.name", "class.fullClassName"] }).map(i => i.obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
let take = req.query.take as unknown as number || 10;
|
||||||
|
let skip = req.query.skip as unknown as number || 0;
|
||||||
|
|
||||||
|
res.json(results.map(i => {
|
||||||
|
return {
|
||||||
|
id: i.id,
|
||||||
|
className: i.class?.className,
|
||||||
|
professor: i.professorObject?.name ?? '',
|
||||||
|
fullClassName: i.fullClassName,
|
||||||
|
textbookCost: i.textbookCost,
|
||||||
|
description: i.description,
|
||||||
|
classLength: i.classLength,
|
||||||
|
dateCreated: i.dateCreated.toISOString(),
|
||||||
|
fileName: `${i.id}.${mime.extension(i.mimeType)}`,
|
||||||
|
class: {
|
||||||
|
className: i.class?.className ?? '',
|
||||||
|
fullClassName: i.class?.fullClassName ?? '',
|
||||||
|
discipline: i.class?.discipline?.name ?? '',
|
||||||
|
},
|
||||||
|
professorId: i.professorObject?.id ?? '',
|
||||||
|
school: {
|
||||||
|
name: i.school?.name ?? '',
|
||||||
|
id: i.school?.id ?? '',
|
||||||
|
fullName: i.school?.fullName ?? '',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}).slice(skip).slice(0, take));
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
res.status(500).json({ msg: "Internal server error" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/report/:id", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { reportType, reportTitle, reportBody, reportBy } = req.body;
|
||||||
|
const syllabusId = req.params.id;
|
||||||
|
|
||||||
|
// Check if the syllabus exists
|
||||||
|
const syllabus = await prisma.syllabus.findUnique({ where: { id: syllabusId } });
|
||||||
|
if (!syllabus) {
|
||||||
|
return res.status(404).json({ msg: "Syllabus not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the report
|
||||||
|
const report = await prisma.report.create({
|
||||||
|
data: {
|
||||||
|
reportType,
|
||||||
|
reportTitle,
|
||||||
|
reportBody,
|
||||||
|
reportBy,
|
||||||
|
syllabusId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json(report);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
res.status(500).json({ msg: `Internal Server Error ${e}` });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.post("/moderator/login", async (req, res) => {
|
||||||
|
const { username, password } = req.body;
|
||||||
|
|
||||||
|
if (!username || !password) {
|
||||||
|
return res.status(400).json({ msg: "Missing username or password" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const moderator = await prisma.moderator.findUnique({ where: { username } });
|
||||||
|
if (!moderator) {
|
||||||
|
return res.status(401).json({ msg: "Invalid username or password" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const valid = await argon2.verify(moderator.password, password);
|
||||||
|
if (!valid) {
|
||||||
|
return res.status(401).json({ msg: "Invalid username or password" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = jwt.sign({ id: moderator.id }, process.env.SECRET_KEY as string, { expiresIn: '2h' });
|
||||||
|
|
||||||
|
res.json({ token });
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/moderator/flag", jwtMiddleware, async (req, res) => {
|
||||||
|
res.json(req.body);
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise<void>((resolve) =>
|
||||||
|
httpServer.listen({ port: 4000, host: "0.0.0.0" }, resolve)
|
||||||
|
);
|
||||||
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "sms-server",
|
||||||
|
"module": "index.ts",
|
||||||
|
"type": "module",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest",
|
||||||
|
"typescript": "^5.7.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@graphql-tools/schema": "^10.0.23",
|
||||||
|
"@graphql-typed-document-node/core": "^3.2.0",
|
||||||
|
"@prisma/client": "^6.3.1",
|
||||||
|
"@types/cors": "^2.8.17",
|
||||||
|
"@types/express-fileupload": "^1.5.1",
|
||||||
|
"@types/jsonwebtoken": "^9.0.9",
|
||||||
|
"@types/mime-types": "^2.1.4",
|
||||||
|
"argon2": "^0.41.1",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^16.4.7",
|
||||||
|
"express": "^4.21.2",
|
||||||
|
"express-fileupload": "^1.5.1",
|
||||||
|
"fuzzysort": "^3.1.0",
|
||||||
|
"g4f": "^1.4.6",
|
||||||
|
"gpt4free": "^0.0.1",
|
||||||
|
"graphql": "^16.10.0",
|
||||||
|
"graphql-tag": "^2.12.6",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"jwt": "^0.2.0",
|
||||||
|
"pg": "^8.13.1",
|
||||||
|
"prisma": "^6.3.1",
|
||||||
|
"spamscanner": "^5.1.5"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "bun index.ts",
|
||||||
|
"dev": "ts-node-dev --respawn --transpile-only index.ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
// This is your Prisma schema file,
|
||||||
|
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||||
|
|
||||||
|
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
|
||||||
|
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
|
||||||
|
|
||||||
|
//TODO: add functionality for other colleges to add classes
|
||||||
|
//TODO: make teachers have an object
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TextbookCost {
|
||||||
|
free
|
||||||
|
cheap // 30 dollars and less
|
||||||
|
moderate // 30-60 dollars
|
||||||
|
expensive // beyond 60 dollars
|
||||||
|
}
|
||||||
|
|
||||||
|
model Discipline {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String @unique
|
||||||
|
classes Class[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Class {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
className String @unique
|
||||||
|
fullClassName String @default("Full Class Name")
|
||||||
|
syllabi Syllabus[]
|
||||||
|
discipline Discipline @relation(fields: [disciplineId], references: [id])
|
||||||
|
disciplineId String
|
||||||
|
school School? @relation(fields: [schoolId], references: [id])
|
||||||
|
schoolId String?
|
||||||
|
professors Professor[] @relation("ProfessorClasses")
|
||||||
|
}
|
||||||
|
|
||||||
|
model School {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String @unique
|
||||||
|
fullName String @default("")
|
||||||
|
classes Class[]
|
||||||
|
professors Professor[]
|
||||||
|
syllabi Syllabus[]
|
||||||
|
|
||||||
|
ClassSubmission ClassSubmission[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Professor {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String @unique
|
||||||
|
classes Class[] @relation("ProfessorClasses")
|
||||||
|
rmp String?
|
||||||
|
syllabi Syllabus[]
|
||||||
|
school School? @relation(fields: [schoolId], references: [id])
|
||||||
|
schoolId String?
|
||||||
|
}
|
||||||
|
|
||||||
|
model Syllabus {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
|
||||||
|
submission ClassSubmission? @relation(fields: [submissionId], references: [id])
|
||||||
|
submissionId String?
|
||||||
|
|
||||||
|
school School? @relation(fields: [schoolId], references: [id])
|
||||||
|
schoolId String?
|
||||||
|
// Whether or not the syllabus has been reviewed by a Tier 2 moderator
|
||||||
|
reviewed Boolean @default(false)
|
||||||
|
|
||||||
|
dateCreated DateTime @default(now())
|
||||||
|
|
||||||
|
// Class length (weeks)
|
||||||
|
classLength Int?
|
||||||
|
|
||||||
|
// Course description
|
||||||
|
description String?
|
||||||
|
|
||||||
|
// Creator data
|
||||||
|
createdByEmail String?
|
||||||
|
createdByName String?
|
||||||
|
|
||||||
|
// File type
|
||||||
|
mimeType String // TODO: remove?
|
||||||
|
|
||||||
|
// Markdown content
|
||||||
|
content String?
|
||||||
|
|
||||||
|
professor String
|
||||||
|
|
||||||
|
textbookCost TextbookCost @default(free)
|
||||||
|
fullClassName String @default("Full Class Name") // Full name of class (e.g. "Critical Thinking & Writing")
|
||||||
|
reports Report[] // User reports
|
||||||
|
class Class? @relation(fields: [classId], references: [id])
|
||||||
|
classId String?
|
||||||
|
changes Change[] // Potential changes made by moderators
|
||||||
|
flags Flag[]
|
||||||
|
|
||||||
|
Professor Professor[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model ClassSubmission {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
dateCreated DateTime @default(now())
|
||||||
|
|
||||||
|
school School? @relation(fields: [schoolId], references: [id])
|
||||||
|
schoolId String?
|
||||||
|
className String
|
||||||
|
professor String
|
||||||
|
classLength Int?
|
||||||
|
description String?
|
||||||
|
createdByEmail String?
|
||||||
|
createdByName String?
|
||||||
|
textbookCost TextbookCost @default(free)
|
||||||
|
|
||||||
|
syllabusFileType String
|
||||||
|
|
||||||
|
Syllabus Syllabus[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Moderator {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
username String @unique
|
||||||
|
password String
|
||||||
|
changes Change[]
|
||||||
|
flags Flag[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Flag {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
reason String
|
||||||
|
date DateTime @default(now())
|
||||||
|
moderator Moderator @relation(fields: [moderatorId], references: [id])
|
||||||
|
moderatorId String
|
||||||
|
|
||||||
|
syllabusId String @unique
|
||||||
|
syllabus Syllabus @relation(fields: [syllabusId], references: [id])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Change {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
reason String
|
||||||
|
date DateTime @default(now())
|
||||||
|
moderator Moderator @relation(fields: [moderatorId], references: [id])
|
||||||
|
moderatorId String
|
||||||
|
|
||||||
|
syllabusId String @unique
|
||||||
|
syllabus Syllabus @relation(fields: [syllabusId], references: [id])
|
||||||
|
|
||||||
|
className String?
|
||||||
|
professor String?
|
||||||
|
reviewed Boolean @default(false)
|
||||||
|
dateCreated DateTime @default(now())
|
||||||
|
rating Int?
|
||||||
|
ratingNumber Int @default(0)
|
||||||
|
classLength Int?
|
||||||
|
description String?
|
||||||
|
textbookCost TextbookCost @default(free)
|
||||||
|
fullClassName String @default("Full Class Name")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Report {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
reportType String
|
||||||
|
reportTitle String
|
||||||
|
reportBody String
|
||||||
|
reportDate DateTime @default(now())
|
||||||
|
reportBy String
|
||||||
|
syllabusId String
|
||||||
|
syllabus Syllabus @relation(fields: [syllabusId], references: [id])
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
import argon2 from "argon2";
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
let username = Bun.argv[2];
|
||||||
|
let password = Bun.argv[3];
|
||||||
|
let priviledge = Bun.argv[4];
|
||||||
|
|
||||||
|
try {
|
||||||
|
let newModerator = await prisma.moderator.create({
|
||||||
|
data: {
|
||||||
|
username: username,
|
||||||
|
password: await argon2.hash(password),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(`Successfully created new moderator with username: ${username} and id: ${newModerator.id}`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import jwt, { JwtPayload } from 'jsonwebtoken';
|
||||||
|
import "dotenv/config";
|
||||||
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
|
||||||
|
export function jwtMiddleware(req: Request, res: Response, next: NextFunction): void {
|
||||||
|
const token = req.header('Authorization');
|
||||||
|
if (!token) {
|
||||||
|
res.status(401).json({ error: 'Access denied' })
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const decoded = jwt.verify(token.replace("Bearer ", ''), process.env.SECRET_KEY as string) as JwtPayload;
|
||||||
|
res.locals.userId = decoded.userId;
|
||||||
|
next();
|
||||||
|
} catch (error) {
|
||||||
|
res.status(401).json({ error: 'Invalid token' });
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { Router } from "express";
|
||||||
|
import fileUpload from "express-fileupload";
|
||||||
|
import mime from "mime";
|
||||||
|
import { prisma } from "..";
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.get("/", async (req, res) => {
|
||||||
|
if (req.files === null || req.files === undefined)
|
||||||
|
return res.status(400).json({ msg: "No file uploaded" });
|
||||||
|
if (req.files.upload === null)
|
||||||
|
return res.status(400).json({ msg: "No file uploaded" });
|
||||||
|
if (Array.isArray(req.files.upload))
|
||||||
|
return res.status(400).json({ msg: "Multiple files uploaded" });
|
||||||
|
const file = req.files.upload as fileUpload.UploadedFile;
|
||||||
|
|
||||||
|
if (![mime.lookup(".pdf"), mime.lookup(".docx")].includes(file.mimetype)) return res.status(400).json({ msg: "Invalid file type" });
|
||||||
|
if (file.size > 5_000_000) {
|
||||||
|
return res.status(400).json({ msg: "File too large (max 5MB)" });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Add error handling for missing required fields
|
||||||
|
if (!req.body.schoolName || !req.body.className || !req.body.professor) {
|
||||||
|
return res.status(400).json({ msg: "Missing required fields" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add proper error handling for file operations
|
||||||
|
try {
|
||||||
|
let school = await prisma.school.findUnique({
|
||||||
|
where: { name: req.body.schoolName },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!school) {
|
||||||
|
return res.status(404).json({ msg: "School not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
let classRecord = await prisma.class.findFirst({ // Change findUnique to findFirst
|
||||||
|
where: {
|
||||||
|
className: req.body.className,
|
||||||
|
schoolId: school.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!classRecord) {
|
||||||
|
return res.status(404).json({ msg: "Class not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
let professorObject = await prisma.professor.findUnique({
|
||||||
|
where: { name: req.body.professor },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!professorObject) {
|
||||||
|
professorObject = await prisma.professor.create({
|
||||||
|
data: {
|
||||||
|
name: req.body.professor,
|
||||||
|
schoolId: school.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let { id } = await prisma.syllabus.create({
|
||||||
|
data: {
|
||||||
|
mimeType: file.mimetype,
|
||||||
|
createdByName: req.body.name || undefined,
|
||||||
|
createdByEmail: req.body.email || undefined,
|
||||||
|
fullClassName: req.body.fullClassName || undefined,
|
||||||
|
classLength: req.body.classLength ? parseInt(req.body.classLength, 10) : undefined,
|
||||||
|
textbookCost: req.body.textbookCost || undefined,
|
||||||
|
description: req.body.description || undefined,
|
||||||
|
content: "", // Add the content property
|
||||||
|
class: { connect: { id: classRecord.id } },
|
||||||
|
professorObject: { connect: { id: professorObject.id } },
|
||||||
|
school: { connect: { id: school.id } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await file.mv(`./syllabi/${id}.${mime.extension(file.mimetype)}`);
|
||||||
|
return res.status(200).json({ id });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating syllabus:', error);
|
||||||
|
if (error instanceof Error) {
|
||||||
|
return res.status(500).json({ msg: "Internal server error", error: error.message });
|
||||||
|
}
|
||||||
|
return res.status(500).json({ msg: "Internal server error" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
+294
@@ -0,0 +1,294 @@
|
|||||||
|
import { PrismaClient, type Syllabus } from "@prisma/client";
|
||||||
|
import express from "express";
|
||||||
|
import cors from "cors";
|
||||||
|
import fileUpload from "express-fileupload";
|
||||||
|
import http from "node:http";
|
||||||
|
import fuzzysort from "fuzzysort";
|
||||||
|
import mime from "mime-types";
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import argon2 from 'argon2';
|
||||||
|
import { idText } from "typescript";
|
||||||
|
import 'dotenv/config';
|
||||||
|
import { jwtMiddleware } from "./authMiddleware";
|
||||||
|
import uploadSyllabus from "./entities/uploadSyllabus";
|
||||||
|
|
||||||
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
|
const schema = gql`
|
||||||
|
{
|
||||||
|
type Amazing {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const app = express();
|
||||||
|
const httpServer = http.createServer(app);
|
||||||
|
|
||||||
|
export const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
app.use(cors({
|
||||||
|
origin: "*", // TODO change this to the actual domain
|
||||||
|
}))
|
||||||
|
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
app.use(fileUpload());
|
||||||
|
|
||||||
|
app.use('/files', express.static('syllabi'));
|
||||||
|
|
||||||
|
app.use('/create', uploadSyllabus)
|
||||||
|
|
||||||
|
app.get("/schools", async (req, res) => {
|
||||||
|
let colleges = await prisma.school.findMany({});
|
||||||
|
res.json(colleges);
|
||||||
|
})
|
||||||
|
|
||||||
|
app.post("/search/class/", async (req, res) => {
|
||||||
|
let take = req.body.take || 10;
|
||||||
|
let skip = req.body.skip || 0;
|
||||||
|
|
||||||
|
if (await prisma.school.findUnique({ where: { name: req.query.s as string } }) === null) return res.status(404).json({ msg: "School not found" });
|
||||||
|
|
||||||
|
let classes = await prisma.class.findMany({
|
||||||
|
where: {
|
||||||
|
school: {
|
||||||
|
name: req.query.s as string,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
let results = classes;
|
||||||
|
|
||||||
|
if (req.query.q) {
|
||||||
|
results = fuzzysort.go(req.query.q as string, classes, { keys: ["className", "fullClassName"] }).map(i => i.obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
res.json(results.slice(skip, skip + take));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/search/professor/", async (req, res) => {
|
||||||
|
let take = req.body.take || 10;
|
||||||
|
let skip = req.body.skip || 0;
|
||||||
|
|
||||||
|
if (await prisma.school.findUnique({ where: { name: req.query.s as string } }) === null) return res.status(404).json({ msg: "School not found" });
|
||||||
|
|
||||||
|
let professors = await prisma.professor.findMany({
|
||||||
|
where: {
|
||||||
|
school: {
|
||||||
|
name: req.query.s as string,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let results = professors;
|
||||||
|
|
||||||
|
if (req.query.q) {
|
||||||
|
results = fuzzysort.go(req.query.q as string, professors, { keys: ["name"] }).map(i => i.obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(results.slice(skip, skip + take));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/professor/:id", async (req, res) => {
|
||||||
|
let professor = await prisma.professor.findUnique({
|
||||||
|
where: { id: req.params.id },
|
||||||
|
include: {
|
||||||
|
school: true,
|
||||||
|
syllabi: {
|
||||||
|
include: {
|
||||||
|
class: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (professor === null) return res.status(404).json({ msg: "Professor not found" });
|
||||||
|
return res.json({
|
||||||
|
name: professor.name,
|
||||||
|
school: professor.school?.name,
|
||||||
|
syllabi: professor.syllabi.map(i => {
|
||||||
|
return {
|
||||||
|
id: i.id,
|
||||||
|
className: i.class?.className,
|
||||||
|
fullClassName: i.fullClassName,
|
||||||
|
textbookCost: i.textbookCost,
|
||||||
|
classLength: i.classLength,
|
||||||
|
description: i.description,
|
||||||
|
dateCreated: i.dateCreated.toISOString(),
|
||||||
|
fileName: `${i.id}.${mime.extension(i.mimeType)}`,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/syllabus/:id", async (req, res) => {
|
||||||
|
let syllabus = await prisma.syllabus.findUnique({
|
||||||
|
where: { id: req.params.id, reviewed: true },
|
||||||
|
include: {
|
||||||
|
class: {
|
||||||
|
include: {
|
||||||
|
discipline: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
professorObject: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (syllabus === null) return res.status(404).json({ msg: "Syllabus not found" });
|
||||||
|
return res.json({
|
||||||
|
className: syllabus.class?.className,
|
||||||
|
description: syllabus.description,
|
||||||
|
fullClassName: syllabus.fullClassName,
|
||||||
|
textbookCost: syllabus.textbookCost,
|
||||||
|
content: syllabus.content,
|
||||||
|
classLength: syllabus.classLength,
|
||||||
|
professor: syllabus.professorObject?.name,
|
||||||
|
dateCreated: syllabus.dateCreated.toISOString(),
|
||||||
|
fileName: `${syllabus.id}.${mime.extension(syllabus.mimeType)}`,
|
||||||
|
class: {
|
||||||
|
className: syllabus.class?.className ?? '',
|
||||||
|
fullClassName: syllabus.class?.fullClassName ?? '',
|
||||||
|
discipline: syllabus.class?.discipline?.name ?? '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/syllabus/:id/download", async (req, res) => {
|
||||||
|
let syllabus = await prisma.syllabus.findUnique({
|
||||||
|
where: { id: req.params.id, reviewed: true },
|
||||||
|
include: {
|
||||||
|
class: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (syllabus === null) return res.status(404).json({ msg: "Syllabus not found" });
|
||||||
|
res.download(`./syllabi/${syllabus.id}.${mime.extension(syllabus.mimeType)}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/search/", async (req, res) => {
|
||||||
|
try {
|
||||||
|
let syllabi = await prisma.syllabus.findMany({
|
||||||
|
include: {
|
||||||
|
class: {
|
||||||
|
include: {
|
||||||
|
discipline: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
professorObject: true,
|
||||||
|
school: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let results = syllabi;
|
||||||
|
|
||||||
|
if (req.query.s) {
|
||||||
|
results = results.filter(i => i.school?.name === req.query.s);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.query.c) {
|
||||||
|
results = results.filter(i => i.class?.className === req.query.c);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.query.p) {
|
||||||
|
results = results.filter(i => i.professorObject?.name === req.query.p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.query.q) {
|
||||||
|
results = fuzzysort.go(req.query.q as string, results, { keys: ["class.className", "professorObject.name", "class.fullClassName"] }).map(i => i.obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
let take = req.query.take as unknown as number || 10;
|
||||||
|
let skip = req.query.skip as unknown as number || 0;
|
||||||
|
|
||||||
|
res.json(results.map(i => {
|
||||||
|
return {
|
||||||
|
id: i.id,
|
||||||
|
className: i.class?.className,
|
||||||
|
professor: i.professorObject?.name ?? '',
|
||||||
|
fullClassName: i.fullClassName,
|
||||||
|
textbookCost: i.textbookCost,
|
||||||
|
description: i.description,
|
||||||
|
classLength: i.classLength,
|
||||||
|
dateCreated: i.dateCreated.toISOString(),
|
||||||
|
fileName: `${i.id}.${mime.extension(i.mimeType)}`,
|
||||||
|
class: {
|
||||||
|
className: i.class?.className ?? '',
|
||||||
|
fullClassName: i.class?.fullClassName ?? '',
|
||||||
|
discipline: i.class?.discipline?.name ?? '',
|
||||||
|
},
|
||||||
|
professorId: i.professorObject?.id ?? '',
|
||||||
|
school: {
|
||||||
|
name: i.school?.name ?? '',
|
||||||
|
id: i.school?.id ?? '',
|
||||||
|
fullName: i.school?.fullName ?? '',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}).slice(skip).slice(0, take));
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
res.status(500).json({ msg: "Internal server error" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/report/:id", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { reportType, reportTitle, reportBody, reportBy } = req.body;
|
||||||
|
const syllabusId = req.params.id;
|
||||||
|
|
||||||
|
// Check if the syllabus exists
|
||||||
|
const syllabus = await prisma.syllabus.findUnique({ where: { id: syllabusId } });
|
||||||
|
if (!syllabus) {
|
||||||
|
return res.status(404).json({ msg: "Syllabus not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the report
|
||||||
|
const report = await prisma.report.create({
|
||||||
|
data: {
|
||||||
|
reportType,
|
||||||
|
reportTitle,
|
||||||
|
reportBody,
|
||||||
|
reportBy,
|
||||||
|
syllabusId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json(report);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
res.status(500).json({ msg: `Internal Server Error ${e}` });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.post("/moderator/login", async (req, res) => {
|
||||||
|
const { username, password } = req.body;
|
||||||
|
|
||||||
|
if (!username || !password) {
|
||||||
|
return res.status(400).json({ msg: "Missing username or password" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const moderator = await prisma.moderator.findUnique({ where: { username } });
|
||||||
|
if (!moderator) {
|
||||||
|
return res.status(401).json({ msg: "Invalid username or password" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const valid = await argon2.verify(moderator.password, password);
|
||||||
|
if (!valid) {
|
||||||
|
return res.status(401).json({ msg: "Invalid username or password" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = jwt.sign({ id: moderator.id }, process.env.SECRET_KEY as string, { expiresIn: '2h' });
|
||||||
|
|
||||||
|
res.json({ token });
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/moderator/check", jwtMiddleware, async (req, res) => {
|
||||||
|
res.json({ id: req.params.userId});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/moderator/flag", jwtMiddleware, async (req, res) => {
|
||||||
|
res.json(req.body);
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise<void>((resolve) =>
|
||||||
|
httpServer.listen({ port: 4000, host: "0.0.0.0" }, resolve)
|
||||||
|
);
|
||||||
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as readline from 'readline';
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
async function updateFullClassNames(filePath: string) {
|
||||||
|
const fileStream = fs.createReadStream(filePath);
|
||||||
|
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: fileStream,
|
||||||
|
crlfDelay: Infinity,
|
||||||
|
});
|
||||||
|
|
||||||
|
for await (const line of rl) {
|
||||||
|
const [className, fullClassName] = line.split(':').map(part => part.trim());
|
||||||
|
if (className && fullClassName) {
|
||||||
|
try {
|
||||||
|
await prisma.class.update({
|
||||||
|
where: { className },
|
||||||
|
data: { fullClassName },
|
||||||
|
});
|
||||||
|
console.log(`Updated ${className} with fullClassName: ${fullClassName}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error updating ${className}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.$disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the function
|
||||||
|
updateFullClassNames('fullClassNames.txt');
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"target": "es2017",
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"types": ["graphql-tag"],
|
||||||
|
"skipLibCheck": true
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user