Skip to content

feat: add scouting portal with fellowship applications, fellowships, and reports#76

Merged
theanmolsharma merged 37 commits into
mainfrom
feat/scouting-portal
Jun 20, 2026
Merged

feat: add scouting portal with fellowship applications, fellowships, and reports#76
theanmolsharma merged 37 commits into
mainfrom
feat/scouting-portal

Conversation

@theanmolsharma

Copy link
Copy Markdown
Collaborator

Summary

  • Add fellowship application submission and admin review flow (accept/reject)
  • Add fellowships module with onboarding and listing endpoints
  • Add fellowship reports module with CRUD, submission, and admin review flow
  • Add recurring report reminder emails on the 20th, 25th, and 28th of each month for fellows who haven't submitted
  • Add email templates for application lifecycle, report lifecycle, and reminders
  • Consolidate user display name logic into a User entity getter

🤖 Generated with Claude Code

theanmolsharma and others added 15 commits June 12, 2026 15:06
Replace repeated `name || discordGlobalName || discordUserName` fallback
chains with a single `displayName` getter on the User entity. Also expose
displayName in the leaderboard response DTO.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add received, accepted, and rejected email notifications for fellowship
applications with HTML and plain text templates following existing patterns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add entities for fellowship application tracking (type, proposal, status,
reviewer remarks) and fellowship records (post-acceptance onboarding data).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add endpoints for submitting, listing, and reviewing fellowship applications.
Includes admin review with accept/reject, reviewer remarks, and email
notifications on status changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add endpoints for completing fellowship onboarding after application
acceptance, and listing/viewing fellowships for users and admins.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Schedule reminder emails on the 20th, 25th, and 28th of each month
for active fellows who haven't submitted their monthly reports.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lication

The folder hosts the unsigned contract and is where the accepted fellow
uploads their W-8BEN form. The URL is captured at review time, persisted
on the Fellowship, and surfaced via the fellowship API.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restructure the accepted email to mirror the manual onboarding flow:
contract signing, W-8BEN submission with India-specific guidance,
Skydo payment setup, and the in-app onboarding form. Includes the
Drive folder URL captured at review time and CCs fellowship@bitshala.org.
Adds CC support to the mail core/service helpers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds CHANGES_REQUESTED status so reviewers can ask applicants to revise
their proposal with feedback instead of only accepting or rejecting.
Applicants edit the proposal and resubmit via the same submit endpoint,
which clears the previous reviewer trail and loops back to SUBMITTED.

Renames submitDraft/updateDraft to submitApplication/updateApplication
since the same endpoints now also serve changes-requested applications.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- startContract rejects end dates on or before the start date (was
  accepted and produced inverted report periods)
- proposal capped at 20k chars (client caps the two long-form sections
  at 3k each; this bounds the whole serialized payload)
- driveFolderUrl must be an actual Drive folder URL
  (drive.google.com/drive/folders/..., incl. /u/N/), not just any
  drive.google.com link

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GET /fellowship-applications/github/:username returns
{ exists: true | false | null } — null when GitHub could not be
reached (rate limit, network), which clients must treat as unknown,
never as missing. Reuses the existing authenticated Octokit via a new
userExists() on GitHubClassroomClient (404 -> false, other errors
rethrown and mapped to null with a warning log).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
theanmolsharma and others added 11 commits June 12, 2026 21:26
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…oints

- Validated query DTOs for the three admin list endpoints (status/type
  filters, plus month/year/fellowshipId and parent-fellowship type for
  reports)
- Whitelisted sortBy/sortOrder params (createdAt, updatedAt, contract
  dates, amount, reporting period) with NULLS LAST and stable id
  tiebreaker
- Case-insensitive ILIKE search across name-like fields (applicant or
  fellow identity, project name, mentor contact, GitHub profile,
  location) with LIKE-wildcard escaping
- Invalid enum/number query params now fail with 400 instead of a
  Postgres enum-cast 500

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Start-contract now accepts periodMonths (1-24) instead of an explicit
end date and computes the end date from the start date. Adds an
addMonths helper that clamps month-end overflow and works in UTC.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the single free-form markdown `proposal` blob with first-class,
individually-validated columns: title, problemStatement, plan, mentorName,
mentorContact, mentorTestimonial, github and links.

- Entity: drop `proposal`, add the new columns (links as text[])
- DTOs: shared ProposalFieldsDto enforcing upper bounds only, so drafts can
  auto-save partial values; create adds immutable `type`, update omits it
- Submit: full ruleset (required fields, title required, conditional github
  for DEVELOPER, link caps/format/dedup) validated in the service
- GET /:id/proposal now returns the structured shape; list/detail stay lean
- GitHub existence check remains advisory and never blocks submission
- Migration 1775546228053 updated in place; seed migration emits structured
  dummy data

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Onboarding is now collected during the fellowship application instead of
as a separate post-acceptance step. Fellowship becomes a pure contract
record.

- Move onboarding fields onto FellowshipApplication (projectName,
  projectGithubLink, academicBackground, graduationYear,
  professionalExperience, domains, codingLanguages, educationInterests,
  bitcoinContributions, bitcoinMotivation, bitcoinOssGoal, additionalInfo,
  questionsForBitshala) with draft-friendly validation.
- Required at submit for all tracks except additionalInfo/questionsForBitshala;
  projectName, projectGithubLink and codingLanguages are developer-only.
- Write profile fields (location, github) through to the user profile from
  create/update rather than duplicating them on the application.
- Drop the onboarding columns from Fellowship, remove CompleteFellowshipOnboardingDto,
  the PATCH /fellowships/:id/onboarding endpoint and completeOnboarding service
  method; slim FellowshipResponseDto.
- Repoint fellowship and fellowship-report list search off the removed columns.
- Update the initial schema and seed migrations in place.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reports now include a nested `fellowship` field identical field-for-field
to a GET /fellowships list item, reusing FellowshipResponseDto. Eager-load
the fellowship application alongside the user in all report finders and
select it in the list query builder so the serializer has the data.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GET /fellowship-reports/me threw "Cannot read properties of undefined
(reading 'mentorContact')" because the query did not eager-load the
fellowship.application relation that FellowshipResponseDto.fromEntity
depends on. Add application: true to the fellowship relation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Onboarding was merged into the application in af667a6, so the separate
"Complete Onboarding" step in the acceptance email is no longer needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the single markdown `content` blob with structured fields:
`summary` (markdown) and `links` (GitHub PR/issue URLs), plus four
optional reflective questions (challengingWork, keyLearning,
reviewerFeedback, growthGoal).

Drafts are lenient (length caps + GitHub link format only); submit stays
strict on `summary` (non-empty, <=3500 chars) and an ACTIVE fellowship.
Reflective questions are optional and never gate submission. The content
endpoint and create/update DTOs carry the new shape; list/metadata and
review payloads are unchanged. Existing migrations updated in place (data
is disposable) — no new migration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add @googleapis/drive and a single GoogleDriveService wrapper (mirroring
the GitHubClassroomClient pattern) as the only code that touches Drive.
Every call passes supportsAllDrives: true; no permission/sharing calls
exist. The client factory base64-decodes a service-account key.

Add the googleDrive config block (serviceAccountKey, sharedDriveId,
optional rootFolderId), validated like the rest, with placeholders in the
example config.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the FellowshipDocument entity (one current row per application+type),
the FellowshipApplication.driveFolderId column, the FellowshipDocumentType
and FellowshipDocumentStatus enums, and the document-phase FellowshipStatus
values (AWAITING_DOCUMENTS, DOCUMENTS_IN_REVIEW, DOCUMENTS_APPROVED).

All schema changes are folded into this PR's existing migration rather than
a new file, per the design.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
theanmolsharma and others added 11 commits June 17, 2026 14:11
…mail

Rewrite the fellowship-application-accepted templates to link into the app
(via app.frontEndUrl) instead of a Drive folder, and change
sendFellowshipApplicationAcceptedEmail to take the fellowship id and build
that deep link.

Add sendFellowshipDocumentRejectedEmail with its html/text templates and a
TemplateContextMap entry, sent to the fellow with the rejection reason and a
re-upload link.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the single service that proxies all document handling by our own
document UUID: list, download (stream Drive -> backend), upload/re-upload
(new Drive revision), and admin approve/reject. Ownership is enforced per
request and a document must belong to the addressed fellowship, so Drive
IDs never cross an API boundary.

Provisioning, upload and review run in DbTransactionService transactions;
accept-time folder creation is guarded by a pessimistic lock + driveFolderId
re-check so concurrent accepts can't orphan a folder, and Drive writes are
best-effort rolled back on DB failure. Uploads are validated by %PDF- magic
bytes and the stored filename is sanitized.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make PATCH /fellowship-applications/:id/review multipart: accepting now
carries the Bitshala-signed unsigned-contract PDF (replacing the
driveFolderUrl field). The accept branch creates the per-application Drive
folder, uploads the contract, and creates the Fellowship (AWAITING_DOCUMENTS)
plus the three document rows in one transaction, then emails the fellow a
link into the app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…proval

Add the proxied document routes on the fellowships controller — list,
download (StreamableFile with attachment + nosniff), upload by document id
(15 MB PDF FileInterceptor), and admin approve/reject. Gate start-contract
on DOCUMENTS_APPROVED instead of PENDING, and drop driveFolderUrl from
FellowshipResponseDto.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…reports

Require the four report reflection prompts (challenging work, key learning,
reviewer feedback, growth goal) at submit. For applications, require location
on all tracks, make mentor name/contact/testimonial and project name required
for developers and designers (optional for educators), and keep the project
GitHub link developer-only. Update request DTO comments to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Shared, admin-only internal notes on a fellowship application. The table is folded into the undeployed baseline fellowship migration rather than added as a new one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Create, list, edit and delete with application-scoped lookups; edits and deletes are restricted to the note's author.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the notes controller and service into the fellowship applications module. Every route is admin-only via @roles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fold the follow-up migration (nullable applicationId on
fellowship_document/fellowship_application_note + regenerated FK/UQ
names) into the base migration, using TypeORM's generated constraint
names so the schema no longer drifts from the entity metadata. Deletes
the redundant follow-up migration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@theanmolsharma theanmolsharma merged commit 8a0fbb5 into main Jun 20, 2026
2 checks passed
@theanmolsharma theanmolsharma deleted the feat/scouting-portal branch June 20, 2026 06:25
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.

1 participant