Skip to content

feat(#597): implement milestone appeal system in contract and UI#956

Open
jerrymusaga wants to merge 1 commit into
bakeronchain:mainfrom
jerrymusaga:feat/597-milestone-appeal-system
Open

feat(#597): implement milestone appeal system in contract and UI#956
jerrymusaga wants to merge 1 commit into
bakeronchain:mainfrom
jerrymusaga:feat/597-milestone-appeal-system

Conversation

@jerrymusaga
Copy link
Copy Markdown
Contributor

Summary

Closes #597

Adds a formal appeal path for scholars when their milestone is rejected, covering the full stack: Soroban contract state machine, backend API endpoints, DB migration, and frontend UI.

Contract (contracts/course_milestone/src/lib.rs)

  • Two new MilestoneStatus variants: Appealed and FinalRejected
  • AppealData struct (reason + submitted_at) stored under DataKey::MilestoneAppeal
  • appeal_milestone(learner, course_id, milestone_id, reason) — learner-signed; transitions Rejected → Appealed, stores appeal data, emits event
  • resolve_appeal(admin, learner, course_id, milestone_id, approved) — admin-signed; transitions Appealed → Approved (or FinalRejected)
  • get_appeal() read-only query

Database (server/src/db/migrations/021_milestone_appeals.sql)

  • Extends milestone_reports.status CHECK to include appealed and final_rejected
  • Adds appeal_reason TEXT and appeal_submitted_at TIMESTAMPTZ columns
  • Extends milestone_audit_log.decision CHECK to include appeal_approved and appeal_rejected
  • Partial index on status = 'appealed' for admin queue queries

Backend

  • milestone-appeal.controller.ts
    • appealMilestonePOST /api/milestones/:id/appeal: requires scholar JWT + ownership check, validates rejected status, calls milestoneStore.submitAppeal(), notifies SECOND_TIER_VALIDATOR_ADDRESS (env var) via in-app + push notification
    • resolveAppealPOST /api/admin/milestones/:id/resolve-appeal: requires admin auth, validates appealed status; on approve calls callVerifyMilestone to mint LRN and updates to approved; on reject calls emitRejectionEvent and updates to final_rejected; adds audit entry, notifies scholar
  • milestone-appeal.routes.ts: factory router using jwtService for scholar auth
  • milestone-store.ts: new MilestoneReportStatus / MilestoneAuditDecision union types, appeal_reason/appeal_submitted_at fields, submitAppeal() method (Postgres + in-memory)
  • index.ts: registers createMilestoneAppealRouter

Frontend

  • src/types/milestone.ts: MilestoneReportStatus union type, appeal fields on SubmittedMilestoneReport
  • MilestoneSubmitPanel.tsx: new states — rejected (shows "File an Appeal" button + inline reason form), appealed (pulsing warning banner), final_rejected (closed final-decision card)
  • ScholarMilestones.tsx: STATUS_LABELS / STATUS_COLORS maps, coloured status badge, appeal timestamp shown when status is appealed

Test plan

  • Run npm run migrate — confirm 021_milestone_appeals applies cleanly
  • Reject a milestone as admin → scholar's panel shows "File an Appeal" button
  • Submit appeal without reason → expect 400
  • Submit appeal from wrong wallet → expect 403
  • Submit appeal on a pending report → expect 409
  • Submit valid appeal → status in DB becomes appealed, second-tier validator receives notification
  • Attempt to appeal again on an already-appealed report → expect 409
  • Admin visits /admin/milestones/:id — confirm appealed status is visible
  • POST /api/admin/milestones/:id/resolve-appeal with { approved: true } → status → approved, LRN minted, scholar notified
  • POST /api/admin/milestones/:id/resolve-appeal with { approved: false, reason: "..." } → status → final_rejected, scholar notified
  • Frontend: appealed status shows pulsing yellow banner; final_rejected shows closed red card
  • npm run migrate:rollback — confirms undo script drops columns and restores CHECK constraints cleanly

Notes

  • appeal_milestone() on-chain requires learner wallet auth; the backend records the DB state only. Callers who want full on-chain provenance can invoke the contract from the frontend wallet before or after calling this endpoint.
  • SECOND_TIER_VALIDATOR_ADDRESS env var controls who gets notified; if unset, no notification is sent (no error).

…ract, backend, and UI

Contract (course_milestone/src/lib.rs):
- Add Appealed and FinalRejected variants to MilestoneStatus enum
- Add AppealData struct (reason + submitted_at) stored at MilestoneAppeal key
- Add appeal_milestone(): learner transitions Rejected → Appealed, stores reason, emits event
- Add resolve_appeal(): admin transitions Appealed → Approved (mints LRN) or FinalRejected
- Add get_appeal() query function

Backend:
- Migration 021: extend status CHECK (+ appealed, final_rejected), add appeal_reason /
  appeal_submitted_at columns, extend audit decision CHECK (+ appeal_approved, appeal_rejected)
- milestone-store.ts: add MilestoneReportStatus / MilestoneAuditDecision union types,
  appeal fields to MilestoneReport, submitAppeal() method (in-memory + Postgres)
- milestone-appeal.controller.ts: appealMilestone (POST /api/milestones/:id/appeal —
  scholar auth, rejected→appealed, notifies SECOND_TIER_VALIDATOR_ADDRESS) and
  resolveAppeal (POST /api/admin/milestones/:id/resolve-appeal — admin, calls
  callVerifyMilestone on approve or emitRejectionEvent on final reject)
- milestone-appeal.routes.ts: factory router wired with jwtService for scholar auth
- index.ts: register createMilestoneAppealRouter

Frontend:
- types/milestone.ts: MilestoneReportStatus union type, appeal fields on report
- MilestoneSubmitPanel: rejected state shows Appeal button + inline reason form;
  appealed state shows "Under Review" banner; final_rejected shows final decision panel
- ScholarMilestones: STATUS_LABELS/STATUS_COLORS maps, coloured status badge,
  appeal timestamp in submission card
@drips-wave
Copy link
Copy Markdown

drips-wave Bot commented May 30, 2026

@jerrymusaga Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: implement milestone appeal system in contract and UI

1 participant