All Projects
Professional
Ongoing

Medical Toxicology Community

A moderated Q&A community for clinical toxicology — questions, answers, comments, voting, tags and bookmarks — built as an independent service on the Medical Toxicology platform and signed in through the same shared identity as every other product.

Overview

A moderated Q&A community for clinical toxicology — questions, answers, comments, voting, tags and bookmarks — built as an independent service on the Medical Toxicology platform and signed in through the same shared identity as every other product.

PythonDjangoDjango REST FrameworkPostgreSQLRedisCeleryDockerDocker ComposeNginxAWS S3Next.jsReact.jsTypeScriptTanStack QueryZodTailwindCSS

Problem

Toxicology practice generates questions that outlive any single consultation: what to do with an unusual co-ingestion, which protocol a colleague actually follows, how a rare presentation was managed elsewhere. Those answers were living in private messages and group chats — unsearchable, unattributed, and lost the moment a thread scrolled away. A general forum would not do, because clinical content that anyone can publish immediately is a liability, and because a separate login would have cut the community off from the platform the clinicians already used.

Solution

Built a purpose-made Q&A service around three ideas. First, nothing clinical goes public unreviewed: a submitted question lands in a staging model and only becomes a real question once approved, so moderation is a state in the data rather than a cleanup job. Second, the interaction primitives — votes, bookmarks, reports — are generic relations, so the same mechanism works for a question, an answer or a comment without a table per pairing. Third, the service holds no credentials at all: it authenticates against the platform shared JWT cookie and keeps only a user mirror, so a clinician who is already signed in on the main site is already a member here.

Architecture

A standalone Django 5 + DRF backend of seven apps — forum, activity, bookmark, report, product, users, core — with its own PostgreSQL database, Redis and api subdomain, exposing about 41 routes under a single service scope. The forum app models questions, answers, comments, tags and media, plus a temporary-question model that holds submissions awaiting moderation. Votes, bookmarks and reports are Django generic relations keyed by content type and object id, with a uniqueness constraint that makes double-voting impossible at the database level rather than in application code. Denormalized vote sums and view counts keep list queries cheap. Deletion is handled through model-level services that clean up the orphaned media and votes a generic relation would otherwise leave behind. The activity app tracks unseen items and recently viewed questions so the feed can tell a returning user what changed. The frontend is a Next.js 16 + React 19 application with server-side data access, Tailwind v4, TanStack Query and an MDX editor for composing answers.

Challenges

  • Moderating clinical content without a moderation backlog blocking the product: submissions land in a separate staging model and are promoted on approval, so an unreviewed question is structurally incapable of appearing in a public feed.

  • Making votes, bookmarks and reports work uniformly across questions, answers and comments — generic content-type relations with a database-level uniqueness constraint on (user, content type, object) rather than three parallel tables and three parallel rules.

  • Generic relations have no referential integrity, so deleting a question would have left orphaned votes and orphaned S3 media behind — handled with explicit cleanup services invoked from the model delete path.

  • Participating in a nine-service platform without owning identity: the service verifies the shared JWT cookie and mirrors the user by the platform-wide id, so it never stores a password and never diverges from the account the clinician already has.

Outcomes

  • A working Q&A service in production on the platform, with the full question lifecycle — submission, moderation, answers, comments, voting, tagging, bookmarking and reporting — behind about 41 endpoints.

  • Backend architected and written solo across roughly 480 commits; the Next.js frontend built alongside a frontend engineer against that API.

  • Survived the platform-wide consolidation that retired five other products — one of the nine services kept in the final architecture.

  • Clinical discussion moved out of private chats into a searchable, attributed, moderated record tied to real platform accounts.

Lessons Learned

  • Moderation belongs in the data model. A pending-content model makes the unreviewed state impossible to leak; a boolean flag on the live table only makes it unlikely.

  • Generic relations buy real uniformity, and charge for it in referential integrity — every one of them needs an explicit cleanup path written the day it is introduced.

  • Denormalizing a vote sum onto the row it belongs to is the difference between a feed that reads one query and a feed that reads one query per item.

  • A service that owns no identity is simpler to build and safer to run — the right amount of a shared platform to depend on is more than feels comfortable at the start.

Team

Sajjad Fani

Sajjad Fani

You

Software Architect & Backend Engineer

Software Engineer

Mohammad Hadian

Mohammad Hadian

Frontend Engineer

Frontend & Mobile Engineer

Professional
Software Architect & Backend Engineer
Team of 2
February 2025 — Present