TutorLoom
The learning platform for companies, colleges and training institutes. Free and open source, with an Enterprise edition.
翻譯準備中
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證This Spring-based web application streamlines classroom instruction and operations. It uses Java and Spring Boot for the backend, with dependencies managed through Maven (pom.xml). Refer to the README.md for setup details and consult pom.xml for a complete list of dependencies.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗Founded and maintained by John Mortensen, Open Coding Society's Spring project supports students as they progress from learning object-oriented programming in Java to building integrated, data-driven applications.
In the OCS computer science pathway, students move beyond individual classes and methods to explore how software components work together. Spring provides a foundation for building APIs, organizing application logic, mapping objects to persistent data, and connecting Java applications to databases and other services.
The project connects classroom concepts—including classes, objects, encapsulation, and data structures—to the architecture of working applications. Students can examine how controllers, services, persistence layers, and application models fit together, then use those patterns in their own projects.
As students progress into Computer Science A and Honors Capstone, this foundation supports more substantial work: integrating frontend applications with backend services, managing shared data, implementing application workflows, and collaborating on systems that serve real users.
This repository is both a starter for student projects and a codebase to learn from. Students can use it as a template, adapt its components to new problems, and contribute improvements back to Open Coding Society.
The goal is not simply to learn a framework. It is to develop the engineering judgment needed to understand, build, test, and maintain software systems.
Backend UI
Use login with .env setup user to manage and restore data.
API access
Validate system is up by testing an endpoint
Examine JWT Login
Review cookies after accessing a page that needs them (ie Groups)
This Backend UI is to manage adminstrative functions like reseting passwords and managing database content: CRUD, Backup, and Restore.
The site is build on Springboot. The project is primarly used to store and retrieve data through APIs. The site has JWT authorization and implements security. In optimal deployed form the data would be served through a professional database, it supports SQLite for development and deployment verification.
api/groups.http.api/announcement.http.api/ui-builder.http.The Java implementation for CS Pathway is now together in src/main/java/com/open/spring/mvc/toolchain/. The interactive Toolchain Trail agent/session flow is the current integrated station-check implementation. A separate text-report upload and AI-review flow is still present in the code; confirm whether students still use it before removing it. Existing /api/cs-pathway and /api/toolchain routes are preserved even though the Java package and class names are now unified.
Use the REST Client VS Code extension and select the local or deployed environment:
/api/cs-pathway/... compatibility routes for setup reports, help requests, and scores./api/toolchain/... agent/session flow.Authenticate first for requests that require the signed-in game/student. Configure baseUrl, adminUid, and adminPassword under rest-client.environmentVariables in .vscode/settings.json. The .vscode/ directory is Git-ignored; keep passwords and other secrets there locally, never in an .http file or commit. The Toolchain agent example submits deliberately failing sample evidence unless replaced with actual local results. Treat returned pairing codes and agent tokens as credentials. Test only systems and accounts you are authorized to use.
Java 21 or higher is requirement using VSCode tooling.
brew install --cask temurin@21 | Linux sudo apt install openjdk-21-jdkMain.java (if issues: Ctrl+Shift+P → "Java: Reload Projects")Build Commands:
./mvnw clean compile # Build
./mvnw test # Test
./mvnw spring-boot:run # Run
Key Files: Java source (src/main/java/...) | templates and application.properties (src/main/resources/templates/...)
.env file to setup default user passwords to satisfy code in Person.java. Students of OCS should leave users as default until competency is obtained.application.properties. Set DEFAULT_PASSWORD in .env to override it. It must meet the password rule (8+ characters, uppercase, lowercase, number, symbol).person.default-password=${DEFAULT_PASSWORD:DefaultPassword123!}
application.properties ports to be unique for your indivdual project.server.port=8585
socket.port=8589
A new local database is empty, and the app does not create most tables itself (ddl-auto=none). Without this step startup fails with Application run failed and no such table: person.
.env file (see Configuration Requirements above and the .env files section below).init refuses to run while port 8585 is in use.python3 scripts/db_migrate.py status and confirm it says Mode: SQLITE and a path under volumes/.python3 scripts/db_migrate.py init and answer y. This resets only your local SQLite file../mvnw spring-boot:run.Run | Debug option in code, install the Java Extension Pack (by Microsoft) and Spring Boot Extension Pack (by VMware)The .env file provides local environment-specific configuration that overrides application.properties. This file is excluded from git (via .gitignore) to prevent committing sensitive credentials and local settings.
How it works:
application.properties first (production defaults).env which overrides those values.env take precedence over application.propertiesExample .env setup for local development:
Supply credentials for the features you use; they are not all required to start Spring.
The same AI settings apply in production, using either .env or runtime environment variables.
# Default password for seed users and resets (overrides person.default-password)
DEFAULT_PASSWORD=123Qwerty!
# Admin user defaults
ADMIN_NAME=Thomas Edison
ADMIN_UID=toby
[email protected]
ADMIN_SID=0000001
ADMIN_PASSWORD=123Toby!
ADMIN_PFP=/images/toby.png
# Teacher user defaults
TEACHER_NAME=Nikola Tesla
TEACHER_UID=niko
[email protected]
TEACHER_SID=0000002
TEACHER_PASSWORD=123Niko!
TEACHER_PFP=/images/niko.png
# Default user for testing
USER_NAME=Grace Hopper
USER_UID=hop
[email protected]
USER_SID=0000003
USER_PASSWORD=123Grace!
USER_PFP=/images/hop.png
# Convience user defaults
MY_NAME=John Mortensen
MY_UID=jm1021
MY_SID=0000004
[email protected]
# JWT Cookie Settings - Local Development (HTTP)
# These override the production defaults in application.properties
jwt.cookie.secure=false
jwt.cookie.same-site=Lax
# Reference Points (optional - defaults exist in application.properties)
GEMINI_API_URL=https://generativelanguage.googleapis.com/v1beta/models/gemini-flash-latest:generateContent
GROQ_SERVER=https://api.groq.com/openai/v1/chat/completions
PAGES_BOT_UID=pages-bot
[email protected]
# Shared AI API keys (required only for the corresponding system AI features)
GEMINI_API_KEY=your-gemini-api-key-here
# Final system grading fallback; personal Groq keys do not require this key
GROQ_API_KEY=your-groq-key-here
# Model ID available to your Groq accounts, used by personal and system requests
# Optional override; this is also the application.properties default
GROQ_GRADING_MODEL=llama-3.1-8b-instant
# Keys/Tokens (required only for the corresponding features)
# GitHub Reference Points
GITHUB_API_TOKEN=your-github-token-here
GIST_TOKEN=your-github-gist-token-here
GIST_TOKEN_ENCRYPTION_KEY=your-encryption-token
# Personal AI key storage: permanent Base64-encoded 32-byte key
# Generate once with openssl rand -base64 32; preserve it across deployments
AI_KEY_ENCRYPTION_KEY=replace-with-your-permanent-base64-encoded-32-byte-key
# Bot Password
PAGES_BOT_PASSWORD=replace-with-a-strong-local-password
# Email Configuration (optional - overrides application.properties)
# [email protected]
# spring.mail.password=your-app-password
# S3 Bucket Defaults
AWS_BUCKET_NAME=my-bucket
AWS_ACCESS_KEY_ID=local-access-key
AWS_SECRET_ACCESS_KEY=local-secret-key-change-me
AWS_REGION=us-east-2
# if developing locally, set the endoint heree
# AWS_S3_ENDPOINT=http://localhost:9000
GROQ_API_KEY is the final shared/system grading fallback, not a student's personal key.
Students save their own keys through Profile; no manual .env entry or database insert
is needed for each student.AI_KEY_ENCRYPTION_KEY encrypts stored personal Gemini and Groq keys. Generate it once
with openssl rand -base64 32, store it securely, and use the same value on every Spring
instance. Back it up separately from the database. Replacing or losing it makes existing
encrypted keys unreadable and requires users to reconnect.ai_connections at startup independently of Hibernate's ddl-auto=none.
The database account needs permission to create the table and select, insert, update,
and delete its rows. No database reset is needed to enable personal keys.GROQ_GRADING_MODEL selects the model for both personal and shared Groq requests.
It defaults to llama-3.1-8b-instant when omitted. Use a supported model ID with access
and quota for the intended accounts. Setting it explicitly blank disables Groq grading
and personal Groq connections.GROQ_SERVER configures the Groq chat-completions URL; key validation uses the /models
endpoint under the same API base. GEMINI_API_URL likewise applies to personal and
system Gemini grading.GET /api/ai-connections should report
available: true and modelConfigured: true for the intended providers. This confirms
local configuration/storage readiness, not live model access or quota.See Personal AI grading keys for routing, deployment, and testing.
The legacy GAMIFY_API_URL and GAMIFY_API_KEY names are not consumed by the current
Spring implementation and are not required.
Production Configuration:
application.properties (HTTPS settings).env or the Spring runtime's environment;
application defaults do not provide AI API keys or personal-key encryption keys.JWT_COOKIE_SECURE=true).
Restart/redeploy Spring after changing configuration; keep the permanent encryption key unchanged.Important: Never commit the .env file to git. It contains sensitive credentials and local-only settings.
Which database am I on?
application.propertiesdefaults tojdbc:sqlite:volumes/sqlite.dband only switches to MySQL whenDB_URLis set. IfDB_URLis commented out in.env, the deployment is running on that local SQLite file, and that file — not RDS — holds the live data. Always runpython3 scripts/db_migrate.py statusfirst; every command follows the same detection, sobackup,initandrestoreall act on whichever database the app itself uses.
Two deployment shapes are supported, and the scripts handle both. See
SQLite deployment below for the current one; the MySQL procedure that follows applies
once DB_URL is set and RDS becomes the live database again.
Note: the MySQL procedure assumes production is on RDS. Be sure all PRs are merged, pulled and tested before you touch production either way.
Set DB_URL, DB_USERNAME, DB_PASSWORD in .env (pointing at production RDS) and
create a venv with mysql-connector-python installed. mysqldump must be on PATH.
Confirm what you are pointed at, and that the schema translation is sound:
python3 scripts/db_migrate.py status python3 scripts/db_migrate.py check
Pull production into a local SQLite backup. This records production's exact MySQL DDL and row counts alongside the data, and exits non-zero if any table or row is missing.
python3 scripts/db_migrate.py backup
The backup lands in volumes/backups/mysql_backup_<timestamp>.db. Do not proceed if
this command fails -- an incomplete backup is not a valid migration source.
Point your local app at that backup (or at volumes/sqlite.db) and TEST TEST TEST.
Make sure the new code works with real production data.
Verify the new schema builds cleanly on a scratch MySQL database first, if you have one.
On production (cockpit, open/spring):
docker compose downgit pullpython3 scripts/db_migrate.py initLoad your data on top of the new schema:
python3 scripts/db_migrate.py restore --keep-target-schema --backup-file volumes/backups/mysql_backup_.db
This takes a mysqldump rollback point before touching anything, loads only the columns
the old and new schemas share (reporting added/dropped columns), and re-counts every
table afterwards. It exits non-zero if MySQL does not match the backup.
Bring spring up: docker compose up -d --build
mysqlrestore.py without --keep-target-schema rebuilds each table from the MySQL DDL
recorded in the backup -- types, indexes, UNIQUE keys and foreign keys included -- so it
reproduces the source database rather than approximating it:
python3 scripts/db_migrate.py restore --backup-file volumes/backups/mysql_backup_.db
The mysqldump safety dump taken before any destructive run is the faster rollback:
mysql -h -P -u -p < volumes/backups/predrop__.sql
HT_*, HTE_*) and the
Hibernate id-allocation tables (*_seq). The app runs with
spring.jpa.hibernate.ddl-auto=none, so Hibernate will not recreate anything that
gets dropped -- losing *_seq would restart id allocation at 1 and collide with
existing rows, and losing HTE_* breaks every write to an audited entity.--keep-target-schema.With DB_URL unset, production data lives in volumes/sqlite.db. The sequence is the
same shape as the MySQL one, and db_migrate.py picks the SQLite code paths for you:
python3 scripts/db_migrate.py status # confirm Mode: SQLITE
python3 scripts/db_migrate.py backup # must exit 0
docker compose down
git pull
python3 scripts/db_migrate.py init # fresh schema from the JPA entities
python3 scripts/db_migrate.py restore --backup-file volumes/backups/sqlite_backup_<ts>.db
docker compose up -d --build
backup uses SQLite's online backup API, not a file copy. The database runs in WAL mode,
so a cp of sqlite.db can silently miss everything still sitting in the -wal file.
restore keeps the schema init just built and loads only the columns both sides share.
Columns added by your change take their defaults; columns and tables removed by it are
listed explicitly rather than being dropped in silence. *_seq tables come across intact,
so Hibernate keeps allocating ids where it left off instead of restarting at 1.
Rolling back is a file copy, because the backup is a complete database:
docker compose down
cp volumes/backups/sqlite_backup_<ts>.db volumes/sqlite.db
rm -f volumes/sqlite.db-wal volumes/sqlite.db-shm
docker compose up -d
db_migrate.py is the only entry point you need:
| Command | What it does |
|---|---|
status | Prints the configured target and what is currently in it |
check | Round-trips the schema through both translators and fails if they disagree |
backup | Verified backup of the live database (MySQL or SQLite) |
init | Rebuilds the schema on the target with Hibernate |
restore | Loads a backup back into the target |
Underneath, mysqlbackup.py / mysqlrestore.py (MySQL) and sqlite_migrate.py (SQLite)
hold the backup and restore implementations and can be run directly -- db_migrate.py calls straight into them, so
there is one implementation, not two. Everything shared between them (reading .env,
parsing DB_URL, opening connections, the backup metadata table name) lives in
mysql_common.py, so the two scripts cannot disagree about which database they are
talking to.
Why check exists. The MySQL-to-SQLite and SQLite-to-MySQL type maps are inverse
functions living in two different files. Nothing structural forces them to stay inverse,
and when they drifted the round trip quietly turned every VARCHAR and DATETIME column
into LONGTEXT. check runs every table in schema_full.txt through both directions and
fails on any degradation. It needs no database and no driver, so it is safe to run
anywhere, including CI. Run it after any change to either map.
schema_full.txt is a point-in-time snapshot, so it goes stale as entities are added.
It is fixture data for check and nothing else — no part of the application reads it.
To check against the schema that actually exists right now:
python3 scripts/db_migrate.py check --live
db_prod2local.py, db_local2prod.py, db_mysql2local.py, db_local2mysql.py and
db_prod_to_mysql.py predated the MySQL migration and have been deleted. If you find
one in an old branch or a stale checkout, do not run it. db_local2mysql.py in particular
carried its own independent MySQL writer that received none of the schema, safety-dump or
row-count fixes -- running it against production would reintroduce every bug those fixes
addressed. They remain recoverable from git history if you ever need to read them.
The current scripts are: db_migrate.py (entry point), mysql_common.py (shared config
and connections), mysqlbackup.py / mysqlrestore.py (MySQL), sqlite_migrate.py
(SQLite), db_init.py (schema rebuild) and migration_utils.py (Spring Boot runner).
These seven migration files are byte-identical to the ones in spring. A fix applied to one
repo belongs in the other -- check both before you consider a migration bug closed.
The step-by-step production procedure -- with the gates, the rollback paths, and the audit of what was wrong with the old scripts -- lives at https://pages.opencodingsociety.com/documentation/migration-runbook. Read it before your first migration; this section is the summary.
If you are developing a component of Spring that requires AWS S3 bucket connections, you can emulate the bucket locally with RustFS. You will need to install Docker (or a similar containerization software).
docker-compose -f rustfs.yml up -d
.env:AWS_S3_ENDPOINT=http://localhost:9000
http://localhost:9001 with these keys:RUSTFS_ACCESS_KEY=local-access-key # ID
RUSTFS_SECRET_KEY=local-secret-key-change-me # password
my-bucket (or whatever you set AWS_BUCKET_NAME to).spring and check if messages are written to the bucket via the Group Chat service.POST http://127.0.0.1:8585/authenticate
Headers: Content-Type: application/json
Body:
{
"uid": "toby",
"password": "123Toby!"
}
Action: Send request → Copy jwt_java_spring token from Cookies tab
POST http://127.0.0.1:8585/api/grade-frqs
Headers: Cookie: jwt_java_spring=YOUR_TOKEN_HERE
Action: Send request
my-app-gist)gist