Oct 13, 2026
5 min read
- Open Source
- NestJS
- MongoDB
- Next.js
- Security
- Community
SOS Academy helps beginners learn by working on real open-source projects, with a mentor. Why the idea works, the features I built for its platform, and what I learned.

The project
In one sentence: Shinobi Open Source Academy ("SOS Academy") is a free community that helps new developers learn by working on real open-source software, with a mentor at their side.
Many people learn to code from videos and tutorials, then hit a wall: they know the basics, but they've never worked on a real project with real code reviews. Companies ask for experience; beginners can't get it. SOS Academy offers a way through: learners join small groups ("squads") led by an experienced developer, pick real tasks from real open-source projects on GitHub, and get guidance until their work is accepted.
The academy's web platform — the site, the admin panel, the blog and the learners' dashboard — is itself open source, and that's what I contributed to.
What makes it innovative
- Real work instead of exercises. Learners fix real problems in real projects, so their work ends up in software people use — and on their public profile.
- A mentor who actually follows you. Small groups mean a mentor sees each learner's real progress.
- Progress tracked automatically. When a learner's work is submitted, reviewed or accepted on GitHub, the platform updates by itself.
- Learners can improve the platform too. It's open source, so contributing to the academy is a first real contribution.
In short: my contribution and what I learned
My part: I built features for the platform: the mentoring groups (who mentors whom, with a size limit that holds even when many people sign up at the same second), the tracking of each learner's task from "claimed" to "accepted", updated automatically from GitHub, and several security fixes — including a sign-in system that could have accepted old keys, and user-management pages that were open to anyone. The first part is accepted; the rest is under review, two parts already approved.
What I took away: when many people act at the same moment, the rules must be enforced by the system itself, not by hoping requests arrive one by one. And building features for a community means discussing, splitting work into steps and listening to reviewers.
The rest of this article goes into the technical details, for developers.
Under the hood
A TypeScript monorepo — NestJS + MongoDB API, Next.js apps (site, admin, blog, learner portal), a shared library, deployed with Docker and Vercel.
In detail: what I brought
My contribution in numbers: 14 pull requests. The Squad model is merged; the others are open, two of them already approved. Unlike in other projects, most of this is new features rather than bug fixes.
Squads
The first PR (#221, merged) defines the Squad model: a mentor, a community, members and a capacity, with rules such as "a mentor must really be a mentor" and "a mentor can't be a member of their own squad". The next ones add the admin endpoints and a mentor's roster page.
The interesting part is concurrency. Capacity is checked inside the update itself, so three people joining a one-place squad at the same moment produce exactly one success. "One active squad per learner" is enforced by a partial unique index — without the partial filter, every empty squad would collide with every other one.
An issue lifecycle as a state machine
Each issue moves through statuses — claimed, in progress, PR open, changes requested, merged… — declared in a single transition table. Every change is a compare-and-set that also writes its history entry:
findOneAndUpdate(
{ _id: id, status: current },
{ $set: { status: next }, $push: { statusHistory: entry } },
)
Five people claiming the same issue at once: exactly one wins, the others get a clear 409. A sync job then reads each issue's GitHub timeline, finds the pull request opened by the assignee and moves the issue accordingly. To jump from "in progress" to "merged", it searches the transition graph breadth-first, so even sync-driven jumps go through legal, recorded steps. It respects GitHub's rate limits and does nothing when nothing changed upstream.
Authentication and security
/auth/refreshalways failed: the cookie parser was never registered. Fixing it revealed something subtle — bcrypt only reads the first 72 bytes, and a user's refresh tokens share their first 72 bytes, so old tokens would have been accepted. They're now hashed with SHA-256.- User-management routes, including
DELETE /users/:id, were open to anyone. They now require an admin session or the user themselves, and a user can't change their own role. pnpm auditwent from 232 advisories (5 critical) to 5 moderate, and the CI audit can now actually fail a build.
Technical lessons
- Let the database enforce the rules. Checking capacity in the service and then updating leaves a gap two requests can slip through. A conditional update or an index closes it for good.
- State machines make illegal states impossible. One table of transitions answered questions that would otherwise be scattered across
ifs — and made the GitHub sync easy to reason about. - Know your crypto's limits. bcrypt is the right tool for passwords and the wrong one for long tokens with identical prefixes. The bug only appeared because I fixed the one in front of it.
- Security debt hides in "internal" routes. Open user-management endpoints are the kind of thing nobody notices until someone does.
- Contributing to a community is different from fixing bugs. Features meant discussing scope, stacking PRs that depend on each other, and answering reviewers like CREDO23 — whose catch on role inheritance made the guard correct.