Data model
CS30 keeps state in two places, and the split is deliberate:
- The database holds course structure and login sessions. This is the relational, queryable state.
- Git repositories hold student work and problem content. This is the file state: code, submissions, activity logs, and problem statements.
Database
The JPA entities are in backend/src/main/models/. Course, ScheduledLab, and Problem are all in Course.kt; LoginSession is in LoginSession.kt.
erDiagram
courses ||--o{ scheduled_labs : has
scheduled_labs ||--o{ problems : has
courses ||--o{ course_students : enrolls
courses {
string id PK
string code
int section
int course_year
string semester
datetime startDate
datetime endDate
string language
string studentGitRepo
string problemGitRepo
string taEmail
}
scheduled_labs {
string id PK
int labNumber
datetime startDateTime
datetime endDateTime
string course_id FK
}
problems {
string id PK
string name
string language
string lab_id FK
}
course_students {
string course_id FK
string student_email
}
login_sessions {
string token PK
string student_email
string ip_address
string platform
datetime logged_in_at
datetime last_heartbeat_at
datetime logged_out_at
}
Notes on the tables:
coursescarries the two git repo paths (studentGitRepo,problemGitRepo) as plain strings. These are filesystem paths on the server, which is why the backend has to run on the same host as the repos.course_studentsis an element-collection table, not an entity. It is just the set of enrolled student emails for a course.- A
ScheduledLabis “active” when now is between its start and end times. That is computed in code (a transient property), not stored. The lab window checks inCodeServiceandProblemServicerely on it. login_sessionsis the session store. The primary key is the token. Every login inserts a new row, and old rows are kept withlogged_out_atset, so the table is a full login history rather than a snapshot of current sessions. It is indexed bystudent_emailbecause that is what most lookups use.ip_addressisrequest.remoteAddr, which is only meaningful because the server terminates TLS directly with no proxy in front.
The schema is managed by Hibernate with ddl-auto=update. There is no Flyway or Liquibase. This has a real consequence for rollbacks: an older jar can expect a different schema than the one in the database, so rolling a jar back across a schema change is risky.
Git storage
GitService writes and commits all student work. The layout inside the student git repo is:
section_<section>/
lab_<labNumber>/
<problemName>/
<studentEmail>/
autosaved-solution.<ext> # latest in-progress code
submissions/
submission-<timestamp>.<ext> # one per submit
result-<timestamp>.json # judge result for that submit
bestsubmission.json # metadata for the best result so far
ActivityLogs/
<date>/
<studentEmail>_<date>_activity.csv # proctoring events
The problem git repo is a flat pool keyed by problem name:
<problemName>/
index.html # the rendered statement
problem.css
assets/ # images and other statement assets
data/ # test data
For the judge, the same problem pool must contain a problem.yaml and the test data under data/sample/ and data/secret/ (each case a .in and a .ans). The judge is pointed at this pool by path when the backend calls it, so the pool must contain the same problem IDs that the database knows about.
The source of truth for course setup
Courses, labs, sections, and enrollments are defined in a YAML file and loaded through the CLI addcourse command. The database is populated from that YAML. There is a template at templates/courseTemplate.yml and a working example at course.yaml in the repo root. If you want to know the exact YAML shape, read CourseInput in cli/src/main/Main.kt, which is the class it deserializes into.