Added Challenge Series Feature#15
Merged
Merged
Conversation
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces a curriculum-oriented “Challenge Series” feature to the platform, adding persistence, backend APIs, and frontend pages/admin tooling to create, order, and browse curated challenge tracks.
Changes:
- Added DB schema for
challenge_seriesand orderedchallenge_series_challenges, plus rollback and seed/test truncation updates. - Implemented repo/service/HTTP handlers and routes for listing, viewing, and admin CRUD + atomic challenge-order replacement.
- Added frontend series list/detail pages, admin management UI, API client/types, routing, and translations.
Reviewed changes
Copilot reviewed 43 out of 44 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/sql/seed_dummy_data.sql | Truncates new series tables during seed reset. |
| migrations/2026-05-19/001_add_challenge_series.sql | Adds series + series-challenges tables and indexes. |
| migrations/2026-05-19/999_rollback.sql | Rollback drops new series tables. |
| internal/service/wargame_service.go | Adds series lifecycle methods to service layer. |
| internal/service/wargame_service_test.go | Adds service-level tests for series behavior/validation/locking. |
| internal/service/testenv_test.go | Injects ChallengeSeriesRepo + truncates new tables in service tests. |
| internal/service/errors.go | Introduces series-specific service errors. |
| internal/repo/testenv_test.go | Truncates new tables in repo tests. |
| internal/repo/challenge_series_repo.go | New Bun repo for series CRUD, listing, ordering, and detail queries. |
| internal/repo/challenge_series_repo_test.go | Repo tests for CRUD/list/order/exists and error paths. |
| internal/models/challenge_series.go | New DB models for series and join table. |
| internal/http/router.go | Registers public + admin series endpoints. |
| internal/http/integration/testenv_test.go | Wires series repo into HTTP integration test env; updates truncation. |
| internal/http/integration/challenges_test.go | Adds integration tests for series CRUD/order + auth. |
| internal/http/handlers/types.go | Adds request/response types and mapping for series payloads. |
| internal/http/handlers/testenv_test.go | Wires series repo into handler test env; updates truncation. |
| internal/http/handlers/handler.go | Implements series handlers (list/detail/admin CRUD/order replace). |
| internal/http/handlers/handler_test.go | Adds handler unit tests for series endpoints and sorting. |
| internal/http/handlers/errors.go | Maps series errors to HTTP status codes. |
| internal/db/db.go | Adds AutoMigrate models and index creation for series tables. |
| cmd/server/main.go | Instantiates series repo and injects into service at runtime. |
| frontend/src/routes/WriteupEditor.tsx | UI style adjustments (layout/padding). |
| frontend/src/routes/WriteupDetail.tsx | UI style adjustments (layout/padding). |
| frontend/src/routes/Users.tsx | UI style adjustments. |
| frontend/src/routes/CommunityEditor.tsx | UI style adjustments (layout/padding). |
| frontend/src/routes/CommunityDetail.tsx | UI style adjustments (layout/padding). |
| frontend/src/routes/ChallengeSeriesDetail.tsx | New public series detail page (ordered challenges, locked indicators). |
| frontend/src/routes/ChallengeSeries.tsx | New public series list page with pagination UI. |
| frontend/src/routes/Challenges.tsx | Adds “featured series” cards and layout tweaks on challenges page. |
| frontend/src/routes/challenge-detail/ChallengeInfoPanels.tsx | UI style adjustments (author/solver panels). |
| frontend/src/routes/challenge-detail/ChallengeCommentsPanel.tsx | UI style adjustments (comment card styling). |
| frontend/src/routes/admin/Users.tsx | UI style adjustments (padding/container). |
| frontend/src/routes/admin/ChallengeSeriesManagement.tsx | New admin UI to CRUD series and reorder challenges. |
| frontend/src/routes/admin/ChallengeManagement.tsx | UI style adjustments (padding/container). |
| frontend/src/routes/Admin.tsx | Adds new admin tab for series management. |
| frontend/src/locales/ko.json | Adds series and admin-series translation strings. |
| frontend/src/locales/ja.json | Adds series and admin-series translation strings. |
| frontend/src/locales/en.json | Adds series and admin-series translation strings. |
| frontend/src/lib/types.ts | Adds TS types for series APIs. |
| frontend/src/lib/api.ts | Adds API client methods for series endpoints. |
| frontend/src/App.tsx | Adds routing for /series and /series/:id. |
| frontend/public/seedling.svg | Adds icon used in featured series cards. |
| docs/docs/challenges.md | Documents public list/detail series endpoints. |
| docs/docs/admin.md | Documents admin series endpoints (create/update/delete/replace). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+1841
to
+1849
| seen[challengeID] = struct{}{} | ||
| if _, err := s.challengeRepo.GetByID(ctx, challengeID); err != nil { | ||
| if errors.Is(err, repo.ErrNotFound) { | ||
| validator.fields = append(validator.fields, FieldError{Field: fmt.Sprintf("challenge_ids[%d]", i), Reason: "not_found"}) | ||
| continue | ||
| } | ||
|
|
||
| return fmt.Errorf("wargame.ReplaceChallengeSeriesChallenges challenge: %w", err) | ||
| } |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
In the existing platform, challenges could be categorized by level or category. However, after actually using the platform and collecting feedback from students, we found that a curriculum-oriented series feature was needed. This PR implements the series feature.
The database tables that require migration are listed below.
For more details, please refer to the REST API documentation below and the updated source code.
List Challenge Series
GET /api/challenge-seriesQuery parameters:
page(optional, default1)page_size(optional, default20, max100)Response 200
{ "series": [ { "id": 1, "title": "Warmup Challenge Series", "description": "Starter series", "created_at": "2026-05-19T12:00:00Z", "updated_at": "2026-05-19T12:00:00Z" } ], "pagination": { "page": 1, "page_size": 20, "total_count": 1, "total_pages": 1, "has_prev": false, "has_next": false } }Errors:
invalid inputGet Challenge Series Detail
GET /api/challenge-series/{id}Response 200
{ "series": { "id": 1, "title": "Warmup Challenge Series", "description": "Starter series", "created_at": "2026-05-19T12:00:00Z", "updated_at": "2026-05-19T12:00:00Z" }, "challenges": [ { "id": 5, "title": "Warmup 1", "category": "Web", "created_at": "2026-01-24T12:00:00Z", "level": 0, "points": 100, "solve_count": 12, "is_active": true, "is_locked": false, "is_solved": false, "has_file": false, "stack_enabled": false, "stack_target_ports": [] } ] }Notes:
is_locked: true.Errors:
invalid inputchallenge series not foundCreate Challenge Series
POST /api/admin/challenge-seriesHeaders
Request
{ "title": "Warmup Challenge Series", "description": "Starter set" }Response 201
{ "id": 1, "title": "Warmup Challenge Series", "description": "Starter set", "created_at": "2026-05-19T12:00:00Z", "updated_at": "2026-05-19T12:00:00Z" }Errors:
invalid inputinvalid tokenormissing access_token cookieforbiddenchallenge series already existsUpdate Challenge Series
PUT /api/admin/challenge-series/{id}Request fields are optional; omitted fields are unchanged.
Response 200: same schema as create.
Errors:
invalid inputinvalid tokenormissing access_token cookieforbiddenchallenge series not foundchallenge series already existsDelete Challenge Series
DELETE /api/admin/challenge-series/{id}Response 200
{ "status": "ok" }Errors:
invalid tokenormissing access_token cookieforbiddenchallenge series not foundReplace Challenge Series Challenges
PUT /api/admin/challenge-series/{id}/challengesRequest
{ "challenge_ids": [5, 3, 10] }Notes:
Response 200
{ "status": "ok" }Errors:
invalid inputinvalid tokenormissing access_token cookieforbiddenchallenge series not found