Compare commits

..

107 Commits

Author SHA1 Message Date
kschappell 04be62e0da use central cloudflared tunnel
CI / Backend (push) Failing after 14s
CI / Frontend (push) Failing after 1m1s
CI / Docker Build (push) Has been skipped
2025-09-14 20:33:15 +01:00
kschappell 9931efdb05 uk spelling: visualisation 2025-09-14 19:58:37 +01:00
kschappell 53095c7c5b uk spelling: labelled 2025-09-14 19:36:14 +01:00
kschappell 1fff80888a fix: API_URL env for server-side fetches 2025-09-14 19:24:31 +01:00
kschappell ce4d8d0ca6 fix(docker): API URL as build arg 2025-09-14 18:25:50 +01:00
kschappell 79c1eff071 fix: empty public folder in frontend build 2025-09-14 17:28:26 +01:00
kschappell 60e5d400ac update changelog 2025-09-12 16:29:54 +01:00
kschappell e93948db5f viz primitives for data structures 2025-09-12 15:26:17 +01:00
kschappell 4c8f1d92c6 feat(viz): trie visualisation 2025-09-12 15:08:18 +01:00
kschappell 987574f3cb chore(deploy): prod deploy config 2025-09-12 14:57:04 +01:00
kschappell 32d0aa2b07 learning progression + completion tracking 2025-09-12 14:12:38 +01:00
kschappell 9838519113 feat(progress): spaced repetition dashboard 2025-09-12 13:15:06 +01:00
kschappell 79107582f6 feat(content): pattern comparisons 2025-09-10 18:46:50 +01:00
kschappell d55675216a migrate 392 questions to pattern format 2025-09-10 18:05:55 +01:00
kschappell 7a5d079fc6 feat(patterns): pattern taxonomy + is_optimal 2025-09-08 16:03:14 +01:00
kschappell dfe76ad1ee fix: single number → bit-manipulation pattern 2025-09-08 15:49:57 +01:00
kschappell 064e409680 feat(viz): wire up bit manipulation 2025-09-08 14:50:57 +01:00
kschappell 8aa935f6be feat(content): bit manipulation pattern 2025-09-08 14:46:22 +01:00
kschappell c79f4bf29b single number algorithm def 2025-09-08 14:00:13 +01:00
kschappell 102311d110 feat(viz): bit manipulation algorithm component 2025-09-08 13:32:40 +01:00
kschappell fc866ac366 feat(viz): bit manipulation view 2025-09-04 00:36:55 +01:00
kschappell 650f87ec75 feat(viz): bit display primitive 2025-09-04 00:22:32 +01:00
kschappell 8c408ffa9d feat(viz): bit manipulation types 2025-09-03 23:27:24 +01:00
kschappell 7c2232e27c feat(viz): union-find 2025-09-03 22:44:24 +01:00
kschappell b255ccba32 feat(viz): backtracking, greedy, intervals, matrix 2025-09-03 21:47:33 +01:00
kschappell d23ac3534d feat(viz): heap pattern with kth largest 2025-09-03 21:30:46 +01:00
kschappell 9deca3b7ff feat(viz): dp coin change 2025-09-01 21:29:23 +01:00
kschappell a9bbbf4a91 feat(viz): tree/BFS/DFS patterns 2025-09-01 20:49:11 +01:00
kschappell 47082ddb12 fix viz layout stability 2025-08-24 17:43:46 +01:00
kschappell 5f563be93c feat(viz): monotonic stack viz 2025-08-24 17:32:27 +01:00
kschappell cafcf0158d linked list visualisations 2025-08-24 16:33:51 +01:00
kschappell db3f69e0b0 feat(viz): sprint 1 - array visualisations 2025-08-24 16:09:24 +01:00
kschappell 2e4de5cc31 feat(viz): two pointers narrative 2025-08-24 15:30:46 +01:00
kschappell 1031af4e3b feat(viz): interactive algorithm viz system 2025-08-23 20:28:22 +01:00
kschappell 3b3479a7da feat(patterns): strategy tutorials 2025-08-23 19:58:33 +01:00
kschappell 2599f4f95a feat(patterns): data structure tutorials 2025-08-23 19:25:47 +01:00
kschappell 4a469abb96 feat(patterns): pointer/array tutorials 2025-08-18 22:15:43 +01:00
kschappell afbfabe0d4 feat(patterns): graph/tree traversal tutorials 2025-08-18 22:00:08 +01:00
kschappell 2b4ef8b7e7 feat(patterns): tutorial system 2025-08-07 00:41:51 +01:00
kschappell d48a211e3f fix(frontend): make stats pages dynamic 2025-08-07 00:07:22 +01:00
kschappell d268ced579 update changelog 2025-08-06 23:54:33 +01:00
kschappell b577b4d62e feat(content): hidden test cases 2025-08-06 23:21:42 +01:00
kschappell de87136054 seo: sitemap, robots, og tags 2025-08-06 22:38:22 +01:00
kschappell d5d61ffc62 feat(frontend): big O complexity estimator 2025-07-31 15:05:41 +01:00
kschappell 3d7e46c9f7 fix(backend): CORS and API URL fixes 2025-07-31 14:52:27 +01:00
kschappell 5b2b602d85 submit ux improvements 2025-07-31 13:53:39 +01:00
kschappell 4b492db033 feat(frontend): add progress tracking 2025-07-31 13:23:11 +01:00
kschappell eefc2915a4 feat(content): function signatures + test cases 2025-07-13 19:53:34 +01:00
kschappell 2c0a051c10 nit 2025-07-13 19:36:07 +01:00
kschappell 845b4dc3a0 feat(content): more test cases 2025-07-13 19:25:39 +01:00
kschappell 10959773d7 update .env.example and docker config 2025-06-27 20:52:16 +01:00
kschappell 885c7dde2a fix(frontend): handle API errors gracefully 2025-06-27 20:27:54 +01:00
kschappell dfdae97d86 remove duplicate question file 2025-06-20 16:05:43 +01:00
kschappell f1c2c1f199 feat(content): add new patterns 2025-06-20 16:02:40 +01:00
kschappell bd85c951ec extend complexity fields to text 2025-06-20 15:10:37 +01:00
kschappell 68565dc068 bump frontend deps 2025-06-20 14:40:28 +01:00
kschappell 300129bd62 feat(frontend): markdown and detail components 2025-06-20 13:53:44 +01:00
kschappell 3b3a329e3d vitest setup + component tests 2025-05-30 21:16:27 +01:00
kschappell cb0b04c428 loading skeletons 2025-05-30 20:34:31 +01:00
kschappell fc9192937d fix docker build missing readme 2025-05-30 20:00:49 +01:00
kschappell 92399264de feat(frontend): add question search 2025-05-30 19:27:31 +01:00
kschappell 1dd910bdde questions S-W 2025-05-30 19:18:33 +01:00
kschappell 5826a52f7b questions M-R 2025-05-25 12:43:25 +01:00
kschappell d3e71e6f77 questions F-L 2025-05-25 11:47:04 +01:00
kschappell ee914a4855 questions D-E 2025-05-25 11:08:40 +01:00
kschappell 2b69dc473c questions C 2025-05-25 10:16:13 +01:00
kschappell 4cb4acbf53 questions B (backspace - burst-balloons) 2025-05-24 22:06:49 +01:00
kschappell ffa5b97dad questions A (01-matrix - avoid-flood) 2025-05-24 21:40:39 +01:00
kschappell 55c053ac4d feat(content): test cases batch 2 2025-05-24 21:00:16 +01:00
kschappell 78810a9ddb feat(content): test cases batch 1 2025-05-24 20:55:37 +01:00
kschappell ddb20e24b9 feat(frontend): integrate editor into question page 2025-05-24 20:06:43 +01:00
kschappell a5e325d76d submission api client 2025-05-24 19:33:40 +01:00
kschappell 1a6278211d feat(frontend): add code editor 2025-05-21 21:48:52 +01:00
kschappell 82b993b33f pyodide hook for python execution 2025-05-21 21:03:28 +01:00
kschappell 1069a2ed41 feat(backend): data loader handles test cases 2025-05-21 20:41:04 +01:00
kschappell 81ec45ff9b feat(backend): test cases migration 2025-05-21 19:49:22 +01:00
kschappell 19849c938e difficulty filters and clickable badges 2025-05-21 18:52:44 +01:00
kschappell b56e2259a2 badge colour variants 2025-05-07 23:23:35 +01:00
kschappell a8e2012878 feat(frontend): visual improvements for content 2025-05-07 23:01:33 +01:00
kschappell 5e46d797c9 security and accessibility pass 2025-05-07 22:15:29 +01:00
kschappell dcc33990f5 ci pipeline and prod compose 2025-04-29 21:36:54 +01:00
kschappell 09a010415d frontend dockerfile 2025-04-29 21:28:45 +01:00
kschappell e6b5044256 wire up pages 2025-04-29 21:06:13 +01:00
kschappell 1aba5719a8 feat(frontend): add UI components 2025-04-29 20:51:49 +01:00
kschappell 6a67326701 types and api client 2025-04-29 19:57:31 +01:00
kschappell 699c937ccd frontend scaffold 2025-04-28 23:50:56 +01:00
kschappell 2abcf8e4f6 hard questions 2025-04-28 23:19:49 +01:00
kschappell 9a5ef95bab medium tree, graph, dp questions 2025-04-28 23:04:27 +01:00
kschappell 06a8e99755 medium array/string questions 2025-04-28 22:51:54 +01:00
kschappell a186ca71e0 easy questions 2025-04-28 21:47:07 +01:00
kschappell 0ec36469f9 chore(backend): add aiosqlite dep 2025-04-28 20:54:39 +01:00
kschappell 8077ec3d1c test(backend): add unit tests 2025-04-26 17:55:18 +01:00
kschappell 962959291c feat(backend): add data loading scripts 2025-04-26 17:39:10 +01:00
kschappell fc6a4e96f5 init files and module stubs 2025-04-21 22:51:36 +01:00
kschappell beee7c6dd7 backend dockerfile 2025-04-21 22:17:16 +01:00
kschappell c24a9d5116 feat(content): initial question set 2025-04-21 21:17:13 +01:00
kschappell ba7aaedc10 feat(content): add categories and patterns 2025-04-21 20:44:21 +01:00
kschappell 8d4b8784d4 feat(backend): add API routes 2025-04-20 17:52:49 +01:00
kschappell 98d4ca5e0c feat(backend): add service layer 2025-04-20 16:56:56 +01:00
kschappell dc382590a1 feat(backend): add Pydantic schemas 2025-04-15 22:57:23 +01:00
kschappell e8f0e14b66 feat(backend): add Alembic migrations 2025-04-15 22:47:23 +01:00
kschappell 822f120a46 feat(backend): db session config 2025-04-15 21:54:14 +01:00
kschappell e85a7f1f4b feat(backend): add database models 2025-04-11 00:02:04 +01:00
kschappell 4d725f88d1 feat(backend): add FastAPI app entry point 2025-04-10 23:30:50 +01:00
kschappell e1fc5cd650 backend config 2025-04-10 23:04:51 +01:00
kschappell f12aea79c4 docker compose for dev 2025-04-10 22:48:45 +01:00
kschappell 06c47695f0 initial project setup 2025-04-10 22:02:21 +01:00
3 changed files with 5 additions and 141 deletions
+5 -3
View File
@@ -35,8 +35,6 @@ jobs:
run: mypy src/
- name: Test
env:
DATABASE_URL: "sqlite+aiosqlite:///:memory:"
run: pytest --cov=src --cov-report=term-missing --cov-fail-under=80
frontend:
@@ -63,7 +61,7 @@ jobs:
run: npm run lint
- name: Type check
run: npx tsc --noEmit
run: tsc --noEmit
- name: Test
run: npm test
@@ -88,6 +86,8 @@ 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
@@ -95,3 +95,5 @@ jobs:
context: ./frontend
push: false
tags: codetutor-frontend:test
cache-from: type=gha
cache-to: type=gha,mode=max
-89
View File
@@ -177,92 +177,3 @@ 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,55 +215,6 @@ 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: