>_SAVE WHAT YOU LEARN. QUERY IT. QUIZ YOURSELF. OVER HTTP.
LearningCollectionAPI is a Kotlin / Spring Boot REST service — the backing store for the zsh-learn shell plugin. Each learning is one row in a single LearningCollection table (id, learning, category, dateAdded), persisted in MySQL through Spring Data JPA. The service combines two interfaces: a hand-written LearningController exposing ten convenience GET endpoints (add / filter-search / recent / random / dump), and the Spring Data REST auto-generated CRUD surface under /learning backed by LCRepo. You GET /add?learning=… from the shell, GET /filter?learning=… to search, and GET /random or /recent/{count} to quiz. No request bodies, no auth layer — everything is a query-string or path-variable GET.
Quickstart
MySQL on localhost:3306 with a schema named root must be live; the JDK 17 toolchain and Kotlin / Gradle versions are managed by the wrapper:
# clone + boot (Spring Boot default port 8080) git clone https://github.com/MenkeTechnologies/LearningCollectionAPI cd LearningCollectionAPI ./gradlew bootRun # build + run diagnostics, then package ./gradlew build ./gradlew bootJar java -jar build/libs/learningcollection-0.0.1-SNAPSHOT.jar # forge an OCI container image ./gradlew bootBuildImage
Datasource config lives in src/main/resources/application.properties: jdbc:mysql://localhost:3306/root, user root, empty password, UTC server timezone. Once up, save and query from the shell:
# save a learning (category defaults to "programming") curl 'http://localhost:8080/add?learning=zsh+globbing+qualifiers' # search by substring (case-sensitive LIKE %text%) curl 'http://localhost:8080/filter?learning=zsh' # quiz: one random learning, full record curl http://localhost:8080/random # last 5 learnings, text only curl http://localhost:8080/recents/5
Full reference: README on GitHub. OpenAPI / Swagger UI is wired via SpringDoc (springdoc-openapi-ui + springdoc-openapi-data-rest).
The domain — one entity, one table
The entire data model is a single JPA entity, LearningCollection (@Table(name = "LearningCollection")), with four columns. There is no second table, no join, no embedded type.
| Field | Type | JPA mapping | Notes |
|---|---|---|---|
id | Long | @Id @GeneratedValue(IDENTITY) | Auto-increment primary key, defaults to 0 before persist. |
learning | String | @Column | The learning text itself — the thing being saved / searched / quizzed. |
category | String | @Column | Set to the DEFAULT_CAT constant ("programming") by the /add endpoint. |
dateAdded | java.util.Date | @Column | Stamped with Date() at save time. Recency ordering relies on insertion order, not this field. |
The entity implements Serializable (serialVersionUID = 1L) and has both a four-arg constructor (used by /add) and a no-arg constructor (used by JPA / deserialization).
Two interfaces — convenience GETs vs auto CRUD
LearningController (hand-written)
A single @RestController exposing ten @GetMapping endpoints. Designed for shell calls — every operation is a GET with a query param or path variable, no request body. Covers save (/add), substring search (/filter), recency (/recent / /recents), randomness (/random / /randoms), and a raw /dump.
LCRepo (Spring Data REST)
@RepositoryRestResource(path = "learning") over CrudRepository<LearningCollection, Long>. Spring Data REST auto-exposes full CRUD (GET / POST / PUT / PATCH / DELETE) under /learning with HAL pagination — no controller code written for it.
REST surface — LearningController endpoints
Ten convenience endpoints, all GET. “Compressed” below means the response is just the learning text strings; “full” means complete LearningCollection records (id / learning / category / dateAdded). Recency is computed as findAll().reversed() (newest-inserted first), randomness as findAll().shuffled().
| Method | Path | Handler | Returns |
|---|---|---|---|
| GET | /add?learning=<text> | add | Saves a new LearningCollection (category = "programming", dateAdded = now); returns the saved entity. |
| GET | /filter?learning=<text> | filterLearn | Substring search via findAllByLearningContaining; returns matching learning strings. |
| GET | /recents | learningItemRecentShortDefault | Last SHORT_CNT (20) learnings, text only. |
| GET | /recents/{count} | getLearningItemRecentShort | Last {count} learnings, text only. |
| GET | /recent/{count} | getLearningItemRecent | Last {count} learnings, full records. |
| GET | /random | learningItem | One random learning, full record. |
| GET | /randoms | learningItemCountShort | One random learning, text only. |
| GET | /randoms/{count} | getLearningItemCountShort | {count} random learnings, text only. |
| GET | /random/{count} | getLearningItemCount | {count} random learnings, full records. |
| GET | /dump | getDump | Streams mysqldump --extended-insert=FALSE root to the servlet output stream (full MySQL extraction). |
Auto-generated CRUD — /learning
Because LCRepo is annotated @RepositoryRestResource(collectionResourceRel = "learning", path = "learning") and extends CrudRepository, Spring Data REST exposes a standard hypermedia CRUD surface under /learning with zero controller code. findAllByLearningContaining and findAllByCategoryContaining are surfaced as search resources.
# Spring Data REST CRUD (auto-generated from LCRepo)
GET /learning # paged collection (HAL)
GET /learning/{id} # one resource
POST /learning # create (JSON body)
PUT /learning/{id} # replace
PATCH /learning/{id} # partial update
DELETE /learning/{id} # remove
GET /learning/search # exported derived queries
# findAllByLearningContaining, findAllByCategoryContaining
Note that findAllByCategoryContaining exists on the repository and is exposed under the Spring Data REST search resource, but is not wired into any LearningController endpoint — only findAllByLearningContaining is used by the hand-written /filter route.
CORS & configuration
WebConfig implements both WebMvcConfigurer and RepositoryRestConfigurer and opens CORS for all origins on every path (addMapping("/**")) — once for MVC controllers, once for the Spring Data REST layer. This lets the zsh-learn plugin (and any browser client) call the API cross-origin.
| Constant | Value | Used by |
|---|---|---|
SHORT_CNT | 20 | /recents default page size. |
DB_NAME | "root" | Schema name embedded in the DUMP command. |
DEFAULT_CAT | "programming" | Category assigned to every learning created via /add. |
DUMP | "mysqldump --extended-insert=FALSE root" | The shell command /dump executes via Runtime.exec. |
Source layout
src/main/kotlin/com/menketechnologies/learningcollection/ ├── LearningCollectionApplication.kt # @SpringBootApplication bootloader ├── LearningController.kt # @RestController — 10 GET endpoints ├── LearningCollection.kt # @Entity — id | learning | category | dateAdded ├── LCRepo.kt # CrudRepository + @RepositoryRestResource ├── Consts.kt # SHORT_CNT, DB_NAME, DEFAULT_CAT, DUMP └── WebConfig.kt # CORS for MVC + Spring Data REST src/main/resources/ └── application.properties # MySQL datasource + JPA config src/test/kotlin/com/menketechnologies/learningcollection/ └── 23 test files # controller / entity / repo / config coverage
6 main Kotlin files (147 LOC) plus 23 test files (6,155 LOC). The engineering report (report.html) breaks down the test suite, the dialect of each endpoint, and the full dependency manifest.
License & usage
Authored by MenkeTechnologies. Built with Kotlin on the JVM. The persisted data is yours — a plain MySQL table you can /dump at any time. Part of the MenkeTechnologiesMeta umbrella.