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