Compare commits

..

107 Commits

Author SHA1 Message Date
kschappell b16dc63b9b use central cloudflared tunnel
CI / Backend (push) Successful in 15s
CI / Frontend (push) Successful in 48s
CI / Docker Build (push) Successful in 3m6s
2025-09-14 20:33:15 +01:00
kschappell 330fa87e1a uk spelling: visualisation 2025-09-14 19:58:37 +01:00
kschappell d0e4b52f97 uk spelling: labelled 2025-09-14 19:36:14 +01:00
kschappell 227eddcb1c fix: API_URL env for server-side fetches 2025-09-14 19:24:31 +01:00
kschappell a6013efa9e fix(docker): API URL as build arg 2025-09-14 18:25:50 +01:00
kschappell 0eda8596a6 fix: empty public folder in frontend build 2025-09-14 17:28:26 +01:00
kschappell dd24eba69a update changelog 2025-09-12 16:29:54 +01:00
kschappell adf8bcb252 viz primitives for data structures 2025-09-12 15:26:17 +01:00
kschappell 5a4ab27fc5 feat(viz): trie visualisation 2025-09-12 15:08:18 +01:00
kschappell d1208fa8b0 chore(deploy): prod deploy config 2025-09-12 14:57:04 +01:00
kschappell 9ac071764e learning progression + completion tracking 2025-09-12 14:12:38 +01:00
kschappell 2ff6da13b3 feat(progress): spaced repetition dashboard 2025-09-12 13:15:06 +01:00
kschappell 6d489da150 feat(content): pattern comparisons 2025-09-10 18:46:50 +01:00
kschappell c60ae08f56 migrate 392 questions to pattern format 2025-09-10 18:05:55 +01:00
kschappell 13bab63618 feat(patterns): pattern taxonomy + is_optimal 2025-09-08 16:03:14 +01:00
kschappell 5b768f6a21 fix: single number → bit-manipulation pattern 2025-09-08 15:49:57 +01:00
kschappell cd4fc02f44 feat(viz): wire up bit manipulation 2025-09-08 14:50:57 +01:00
kschappell 8333e1777f feat(content): bit manipulation pattern 2025-09-08 14:46:22 +01:00
kschappell 6398b0eec8 single number algorithm def 2025-09-08 14:00:13 +01:00
kschappell 3ef5b138f3 feat(viz): bit manipulation algorithm component 2025-09-08 13:32:40 +01:00
kschappell a5974b8b30 feat(viz): bit manipulation view 2025-09-04 00:36:55 +01:00
kschappell ccc5a51a2d feat(viz): bit display primitive 2025-09-04 00:22:32 +01:00
kschappell 8b3dd02cac feat(viz): bit manipulation types 2025-09-03 23:27:24 +01:00
kschappell f9aa659ee3 feat(viz): union-find 2025-09-03 22:44:24 +01:00
kschappell 6c4b827c36 feat(viz): backtracking, greedy, intervals, matrix 2025-09-03 21:47:33 +01:00
kschappell a9d71de4b1 feat(viz): heap pattern with kth largest 2025-09-03 21:30:46 +01:00
kschappell 97f1d69bcc feat(viz): dp coin change 2025-09-01 21:29:23 +01:00
kschappell 096be9ed68 feat(viz): tree/BFS/DFS patterns 2025-09-01 20:49:11 +01:00
kschappell fcba598a52 fix viz layout stability 2025-08-24 17:43:46 +01:00
kschappell d401326ae3 feat(viz): monotonic stack viz 2025-08-24 17:32:27 +01:00
kschappell c92eb1e7af linked list visualisations 2025-08-24 16:33:51 +01:00
kschappell 18381cea6b feat(viz): sprint 1 - array visualisations 2025-08-24 16:09:24 +01:00
kschappell 3caa628d59 feat(viz): two pointers narrative 2025-08-24 15:30:46 +01:00
kschappell de95910e70 feat(viz): interactive algorithm viz system 2025-08-23 20:28:22 +01:00
kschappell 8b13a22397 feat(patterns): strategy tutorials 2025-08-23 19:58:33 +01:00
kschappell cd09974c2a feat(patterns): data structure tutorials 2025-08-23 19:25:47 +01:00
kschappell 8ddd48508a feat(patterns): pointer/array tutorials 2025-08-18 22:15:43 +01:00
kschappell a94e7f6142 feat(patterns): graph/tree traversal tutorials 2025-08-18 22:00:08 +01:00
kschappell 83bf313305 feat(patterns): tutorial system 2025-08-07 00:41:51 +01:00
kschappell 1a1558cfad fix(frontend): make stats pages dynamic 2025-08-07 00:07:22 +01:00
kschappell 0fa5da224d update changelog 2025-08-06 23:54:33 +01:00
kschappell a1a4eeaed7 feat(content): hidden test cases 2025-08-06 23:21:42 +01:00
kschappell 72f7833c6c seo: sitemap, robots, og tags 2025-08-06 22:38:22 +01:00
kschappell 321bc07eb1 feat(frontend): big O complexity estimator 2025-07-31 15:05:41 +01:00
kschappell cbd16b3f60 fix(backend): CORS and API URL fixes 2025-07-31 14:52:27 +01:00
kschappell 4a1c67022d submit ux improvements 2025-07-31 13:53:39 +01:00
kschappell ca989b6943 feat(frontend): add progress tracking 2025-07-31 13:23:11 +01:00
kschappell a7d29b7cce feat(content): function signatures + test cases 2025-07-13 19:53:34 +01:00
kschappell b434eb3a49 nit 2025-07-13 19:36:07 +01:00
kschappell 0f264ef603 feat(content): more test cases 2025-07-13 19:25:39 +01:00
kschappell e28da5def2 update .env.example and docker config 2025-06-27 20:52:16 +01:00
kschappell 86572f06e9 fix(frontend): handle API errors gracefully 2025-06-27 20:27:54 +01:00
kschappell 167297657a remove duplicate question file 2025-06-20 16:05:43 +01:00
kschappell 8cd7b3e6e0 feat(content): add new patterns 2025-06-20 16:02:40 +01:00
kschappell 35753fe863 extend complexity fields to text 2025-06-20 15:10:37 +01:00
kschappell e043450457 bump frontend deps 2025-06-20 14:40:28 +01:00
kschappell 490ea5edf4 feat(frontend): markdown and detail components 2025-06-20 13:53:44 +01:00
kschappell f2d6982a79 vitest setup + component tests 2025-05-30 21:16:27 +01:00
kschappell 5e45458ca2 loading skeletons 2025-05-30 20:34:31 +01:00
kschappell 4dbb440a76 fix docker build missing readme 2025-05-30 20:00:49 +01:00
kschappell b6167dfc5e feat(frontend): add question search 2025-05-30 19:27:31 +01:00
kschappell ef4c4dcf17 questions S-W 2025-05-30 19:18:33 +01:00
kschappell 0a0feb93b5 questions M-R 2025-05-25 12:43:25 +01:00
kschappell ad320dc703 questions F-L 2025-05-25 11:47:04 +01:00
kschappell 360b5fa255 questions D-E 2025-05-25 11:08:40 +01:00
kschappell 615e3f1291 questions C 2025-05-25 10:16:13 +01:00
kschappell c4662f5001 questions B (backspace - burst-balloons) 2025-05-24 22:06:49 +01:00
kschappell 0b83eff6f8 questions A (01-matrix - avoid-flood) 2025-05-24 21:40:39 +01:00
kschappell 09ec96a282 feat(content): test cases batch 2 2025-05-24 21:00:16 +01:00
kschappell 2e323d7a06 feat(content): test cases batch 1 2025-05-24 20:55:37 +01:00
kschappell 359a7a705f feat(frontend): integrate editor into question page 2025-05-24 20:06:43 +01:00
kschappell c4bd3fc6a8 submission api client 2025-05-24 19:33:40 +01:00
kschappell 5e16637b52 feat(frontend): add code editor 2025-05-21 21:48:52 +01:00
kschappell 7951282376 pyodide hook for python execution 2025-05-21 21:03:28 +01:00
kschappell f9c404b69e feat(backend): data loader handles test cases 2025-05-21 20:41:04 +01:00
kschappell dd9a027c5a feat(backend): test cases migration 2025-05-21 19:49:22 +01:00
kschappell c5cd007c8f difficulty filters and clickable badges 2025-05-21 18:52:44 +01:00
kschappell 02fc080bf8 badge colour variants 2025-05-07 23:23:35 +01:00
kschappell 0167d324b0 feat(frontend): visual improvements for content 2025-05-07 23:01:33 +01:00
kschappell 3604e0a889 security and accessibility pass 2025-05-07 22:15:29 +01:00
kschappell 6e01777678 ci pipeline and prod compose 2025-04-29 21:36:54 +01:00
kschappell eaed337fc1 frontend dockerfile 2025-04-29 21:28:45 +01:00
kschappell f1fb32f44c wire up pages 2025-04-29 21:06:13 +01:00
kschappell 8c5e5faf7a feat(frontend): add UI components 2025-04-29 20:51:49 +01:00
kschappell 861f0f6d51 types and api client 2025-04-29 19:57:31 +01:00
kschappell 4689d69a37 frontend scaffold 2025-04-28 23:50:56 +01:00
kschappell 5032273796 hard questions 2025-04-28 23:19:49 +01:00
kschappell 49c37548c0 medium tree, graph, dp questions 2025-04-28 23:04:27 +01:00
kschappell f8350bfdaf medium array/string questions 2025-04-28 22:51:54 +01:00
kschappell c4856d9948 easy questions 2025-04-28 21:47:07 +01:00
kschappell 2e9c38f71b chore(backend): add aiosqlite dep 2025-04-28 20:54:39 +01:00
kschappell 74fbe2676e test(backend): add unit tests 2025-04-26 17:55:18 +01:00
kschappell 1070b1e1fb feat(backend): add data loading scripts 2025-04-26 17:39:10 +01:00
kschappell e6c971f371 init files and module stubs 2025-04-21 22:51:36 +01:00
kschappell ee22ee7480 backend dockerfile 2025-04-21 22:17:16 +01:00
kschappell b4110d6abd feat(content): initial question set 2025-04-21 21:17:13 +01:00
kschappell e4a71ade59 feat(content): add categories and patterns 2025-04-21 20:44:21 +01:00
kschappell 2f0e2a40a6 feat(backend): add API routes 2025-04-20 17:52:49 +01:00
kschappell 375787aa60 feat(backend): add service layer 2025-04-20 16:56:56 +01:00
kschappell 8a3078ad5c feat(backend): add Pydantic schemas 2025-04-15 22:57:23 +01:00
kschappell 803ec7ca9c feat(backend): add Alembic migrations 2025-04-15 22:47:23 +01:00
kschappell 93554683c7 feat(backend): db session config 2025-04-15 21:54:14 +01:00
kschappell 3577082908 feat(backend): add database models 2025-04-11 00:02:04 +01:00
kschappell ec97f27c61 feat(backend): add FastAPI app entry point 2025-04-10 23:30:50 +01:00
kschappell 69a9183c7f backend config 2025-04-10 23:04:51 +01:00
kschappell f4ffd20fcb docker compose for dev 2025-04-10 22:48:45 +01:00
kschappell 286b1d6d43 initial project setup 2025-04-10 22:02:21 +01:00
3 changed files with 138 additions and 4 deletions
-4
View File
@@ -88,8 +88,6 @@ jobs:
context: ./backend
push: false
tags: codetutor-backend:test
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Build frontend image
uses: docker/build-push-action@v5
@@ -97,5 +95,3 @@ jobs:
context: ./frontend
push: false
tags: codetutor-frontend:test
cache-from: type=gha
cache-to: type=gha,mode=max
+89
View File
@@ -177,3 +177,92 @@ async def test_get_stats(client: AsyncClient, db_session: AsyncSession) -> None:
assert data["by_difficulty"]["easy"] == 1
assert len(data["by_category"]) == 1
assert len(data["by_pattern"]) == 1
async def test_get_pattern_tutorial(client: AsyncClient, db_session: AsyncSession) -> None:
await create_sample_data(db_session)
response = await client.get("/api/patterns/two-pointers/tutorial")
assert response.status_code == 200
data = response.json()
assert data["name"] == "Two Pointers"
assert data["learning_progression"]["warmup"][0]["title"] == "Two Sum"
assert len(data["learning_progression"]["core"]) == 0
async def test_get_pattern_tutorial_not_found(client: AsyncClient) -> None:
response = await client.get("/api/patterns/nonexistent/tutorial")
assert response.status_code == 404
async def test_submit_passing(client: AsyncClient, db_session: AsyncSession) -> None:
data = await create_sample_data(db_session)
q = data["question"]
q.test_cases = {
"visible": [{"input": {"nums": [2, 7], "target": 9}, "expected": [0, 1]}],
"hidden": [{"input": {"nums": [3, 2, 4], "target": 6}, "expected": [1, 2]}],
}
await db_session.commit()
response = await client.post(
"/api/questions/two-sum/submit",
json={
"code": "def solve(nums, t): pass",
"hidden_outputs": [{"test_id": 1, "output": [1, 2]}],
},
)
assert response.status_code == 200
result = response.json()
assert result["passed"] is True
assert result["total_passed"] == 1
async def test_submit_wrong_output(client: AsyncClient, db_session: AsyncSession) -> None:
data = await create_sample_data(db_session)
data["question"].test_cases = {
"visible": [],
"hidden": [{"input": {"nums": [1, 2], "target": 3}, "expected": [0, 1]}],
}
await db_session.commit()
response = await client.post(
"/api/questions/two-sum/submit",
json={
"code": "def solve(nums, t): pass",
"hidden_outputs": [{"test_id": 0, "output": [9, 9]}],
},
)
assert response.status_code == 200
assert response.json()["passed"] is False
async def test_submit_missing_output(client: AsyncClient, db_session: AsyncSession) -> None:
data = await create_sample_data(db_session)
data["question"].test_cases = {
"visible": [],
"hidden": [{"input": {"nums": [1, 2], "target": 3}, "expected": [0, 1]}],
}
await db_session.commit()
response = await client.post(
"/api/questions/two-sum/submit",
json={"code": "def solve(nums, t): pass", "hidden_outputs": []},
)
assert response.status_code == 200
assert response.json()["hidden_results"][0]["error"] == "No output provided"
async def test_submit_no_test_cases(client: AsyncClient, db_session: AsyncSession) -> None:
await create_sample_data(db_session)
response = await client.post(
"/api/questions/two-sum/submit",
json={"code": "pass", "hidden_outputs": []},
)
assert response.status_code == 400
async def test_submit_not_found(client: AsyncClient) -> None:
response = await client.post(
"/api/questions/nonexistent/submit",
json={"code": "pass", "hidden_outputs": []},
)
assert response.status_code == 404
+49
View File
@@ -215,6 +215,55 @@ class TestPatternService:
assert result is None
async def test_get_tutorial(self, db_session: AsyncSession) -> None:
related = Pattern(name="BFS", slug="bfs", description="Breadth-first search")
pattern = Pattern(
name="Two Pointers",
slug="two-pointers",
description="Two pointer technique",
common_mistakes=[
{"title": "Off by one", "description": "Wrong bounds", "fix": "Use <="}
],
variations=[{"name": "Fast/slow", "description": "Different speeds"}],
related_patterns=["bfs"],
visualization_examples=[{"id": "ex1", "title": "Basic", "code": "x=1", "steps": []}],
)
db_session.add_all([related, pattern])
await db_session.flush()
q1 = Question(
title="Easy Q", slug="easy-q", difficulty=Difficulty.EASY, description="Easy."
)
q2 = Question(title="Med Q", slug="med-q", difficulty=Difficulty.MEDIUM, description="Med.")
q3 = Question(
title="Hard Q", slug="hard-q", difficulty=Difficulty.HARD, description="Hard."
)
q1.patterns = [pattern]
q2.patterns = [pattern]
q3.patterns = [pattern]
db_session.add_all([q1, q2, q3])
await db_session.commit()
service = PatternService(db_session)
result = await service.get_pattern_tutorial("two-pointers")
assert result is not None
assert result.name == "Two Pointers"
assert len(result.common_mistakes) == 1
assert len(result.variations) == 1
assert len(result.related_patterns) == 1
assert result.related_patterns[0].slug == "bfs"
assert len(result.learning_progression.warmup) == 1
assert len(result.learning_progression.core) == 1
assert len(result.learning_progression.challenge) == 1
assert len(result.visualization_examples) == 1
async def test_get_tutorial_not_found(self, db_session: AsyncSession) -> None:
service = PatternService(db_session)
result = await service.get_pattern_tutorial("nonexistent")
assert result is None
class TestStatsService:
async def test_get_stats(self, db_session: AsyncSession) -> None: