A diabetes companion for iOS β carb counting, insulin bolus estimation, a shared dish library, and a meal diary, backed by a secure FastAPI service.
β οΈ Medical disclaimer. Medoed is an informational aid for people managing diabetes. Every dose it suggests is an estimate derived from user-supplied parameters and must be confirmed with a physician. It is not a medical device.
Medoed (internal codename DiabetAI) is a full-stack diabetes management app. It helps people who count carbohydrates estimate a mealtime insulin bolus, keep a personal and shared library of dishes with per-ingredient nutrition data, and log what they eat over time.
The iOS client is written entirely in Swift / SwiftUI and supports three sign-in methods (email + one-time code, Google, and Apple). The backend is a FastAPI service backed by PostgreSQL via SQLAlchemy, issuing short-lived JWT access tokens paired with rotating opaque refresh tokens. The insulin math runs on the client using the endocrinology parameters stored in each user's profile (target glucose, insulin sensitivity factor, and per-meal insulin-to-carb ratios).
- Insulin bolus calculator β estimates a food dose (
carbs / IC ratio) plus a correction dose ((glucose β target) / ISF), with per-meal (breakfast / lunch / dinner) insulin-to-carb ratios pulled from the user's profile. - Flexible carb input β log a meal by picking a food category, entering carbs directly, choosing a saved dish, or building it from weighed ingredients.
- Dish library β create dishes as ingredient lists (name, weight, carbs per 100 g); keep them private or publish them for everyone.
- Social layer on dishes β like and favorite public dishes; counts and per-user state are returned with each dish.
- Meal diary β record meals with a timestamp and mixed items (manual carb totals, ingredient lists, or a saved dish scaled to the eaten weight), with server-side carb totalling.
- Multi-provider auth β email with a 6-digit confirmation code over SMTP, Google Sign-In, and Sign in with Apple (including Apple's private-relay / no-email-on-return case).
- Medical profile β target glucose, ISF, and three IC ratios, plus an uploadable avatar.
- Account lifecycle β refresh-token rotation, single-session logout, and full account deletion that cascades all user data.
- iPhone & iPad β adaptive SwiftUI layout with a custom Mulish type scale.
flowchart LR
subgraph iOS["iOS App β Swift / SwiftUI"]
UI[Views & ViewModels]
Calc[Bolus Calculator]
KC[(Keychain\nrefresh token)]
API[APIClient]
end
subgraph Backend["FastAPI Service"]
R[Routers: auth / profile / dishes / meals]
SVC[Services: email Β· google Β· apple Β· meal_builder]
SEC[Security: JWT Β· bcrypt]
end
DB[(PostgreSQL)]
Google[[Google Identity]]
Apple[[Apple ID / JWKS]]
SMTP[[SMTP server]]
Files[/uploads Β· avatars/]
UI --> API
Calc -.uses profile.-> UI
API -- HTTPS / JSON\nBearer JWT --> R
R --> SVC
R --> SEC
R --> DB
SVC --> DB
SVC -- verify id_token --> Google
SVC -- verify identity_token --> Apple
SVC -- send code --> SMTP
R --> Files
KC -. refresh flow .- API
Auth model. Access tokens are JWTs (HS256, 15-minute lifetime) carrying the user id. Refresh tokens are opaque, URL-safe random strings (7-day lifetime) stored as rows in the sessions table and kept in the iOS Keychain. On 401, the client exchanges its refresh token at POST /auth/refresh, which rotates and returns a new pair.
Carb / dose flow. Dishes store ingredients as JSON. When a dish is added to a meal, meal_builder scales every ingredient proportionally to the eaten weight and recomputes carbs server-side. The insulin bolus itself is computed on the client from the profile's ISF and IC ratios β the backend never persists a dose.
| Layer | Technology |
|---|---|
| iOS UI | Swift, SwiftUI, Combine |
| iOS auth SDKs | GoogleSignIn-iOS, Sign in with Apple (AuthenticationServices) |
| iOS storage | Keychain (refresh token), custom ProfileStorage |
| API framework | FastAPI, Starlette, Uvicorn |
| ORM / DB | SQLAlchemy 2.0, PostgreSQL (psycopg2 / asyncpg) |
| Validation | Pydantic v2, pydantic-settings |
| Auth / crypto | PyJWT + python-jose (JWT), Passlib + bcrypt (passwords), google-auth (Google), Apple JWKS verification |
smtplib over SSL |
|
| Config | .env via pydantic-settings |
medoed/
βββ backend/ # FastAPI service
β βββ main.py # App factory, CORS, static /uploads, router wiring
β βββ db.py # SQLAlchemy engine + session dependency
β βββ requirements.txt
β βββ core/
β β βββ config.py # Settings (env-driven)
β β βββ security.py # Password hashing, JWT, current-user dependency
β β βββ auth_tokens.py # Refresh-session expiry helper
β β βββ profile_defaults.py # Default profile + ensure_profile()
β βββ models/ # SQLAlchemy models
β β βββ user.py profile.py session.py
β β βββ dish.py dish_like.py dish_favorite.py
β β βββ meal.py meal_item.py
β βββ schemas/ # Pydantic request/response schemas
β βββ routes/ # auth Β· profile Β· dishes Β· meals
β βββ services/ # auth_email Β· auth_google Β· auth_apple Β· meal_builder
β
βββ frontend/ # iOS app (Swift / SwiftUI)
β βββ app.xcodeproj
β βββ app/
β βββ DiabetAIApp.swift # @main entry
β βββ RootView.swift AppState.swift AppTabsView.swift
β βββ API/ # APIClient + Auth/Dishes/Profile APIs
β βββ Models/ # DTOs + ProfileViewModel
β βββ Pages/ # Auth, Main, Calculator, Dishes, Profile, Diary
β βββ Services/ # Apple/Google auth, token, dish sheets, storage
β βββ Utils/ # Keychain, Constants, iPad layout
β βββ Assets.xcassets, font/ # Mulish font family, icons
β
βββ privacy-policy/ # Static privacy page
Requires Python 3.11+ and a reachable PostgreSQL instance.
cd backend
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env # then fill in real values
# edit .env: DATABASE_URL, JWT_SECRET_KEY, SMTP_*, GOOGLE_*, APPLE_*
uvicorn main:app --reload --port 8000Tables are created automatically on startup via Base.metadata.create_all. Interactive API docs are then available at http://localhost:8000/docs.
Requires Xcode 15+ and iOS 16+.
cd frontend
open app.xcodeprojThen:
- Set your own signing team and bundle identifier in the project settings.
- Point the client at your backend by editing
baseURLinapp/Utils/Constants.swift. - For Google Sign-In, set the reversed-client-id URL scheme in
Info.plistto your own Google iOS OAuth client. - Build and run on a simulator or device.
Base URL depends on your deployment. All bodies are JSON; protected routes require Authorization: Bearer <access_token>.
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/auth/email/send-code |
β | Send a 6-digit registration code by email |
POST |
/auth/email/confirm-register |
β | Confirm code + password β token pair |
POST |
/auth/email/login |
β | Email + password login |
POST |
/auth/google |
β | Sign in / up with a Google id_token |
POST |
/auth/apple |
β | Sign in / up with an Apple identity_token |
POST |
/auth/refresh |
β | Rotate refresh token β new token pair |
POST |
/auth/logout |
β | Invalidate a refresh token |
DELETE |
/auth/account |
β | Delete account and all related data |
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/profile |
β | Get medical profile (target glucose, ISF, IC ratios) |
PUT |
/profile |
β | Replace profile fields |
POST |
/profile/avatar |
β | Upload avatar (JPEG/PNG/WEBP, β€ 5 MB) |
DELETE |
/profile/avatar |
β | Remove avatar |
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/dishes |
β | Create a dish (ingredient list) |
GET |
/dishes |
β | List public dishes + own private dishes |
GET |
/dishes/{id} |
β | Get a dish |
PUT |
/dishes/{id} |
β | Update a dish (author only) |
DELETE |
/dishes/{id} |
β | Delete a dish (author only) |
POST / DELETE |
/dishes/{id}/like |
β | Like / unlike (idempotent) |
POST / DELETE |
/dishes/{id}/favorite |
β | Favorite / unfavorite (idempotent) |
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/meals |
β | Create a meal from mixed items (manual_total / ingredients / dish) |
GET |
/meals |
β | List the user's meals (newest first) |
GET |
/meals/{id}/items |
β | Get the detailed items of a meal |
A complete request/response reference (with error tables and payload shapes) lives in
backend/README.md.
Current state
- β Email / Google / Apple authentication with JWT + rotating refresh tokens
- β Medical profile with avatar upload
- β Dish CRUD with likes and favorites
- β Client-side insulin bolus calculator
- β Meal diary with server-side carb totalling
Planned
- π² Glucose-trend history and charts in the diary view
- π² Push notifications for logging reminders (APNs)
- π² Automated test suites (pytest for the API, XCTest for the client)
- π² Photo-based carb estimation
- π² Localization beyond the current Russian UI
Released under the MIT License. Β© 2026 Egor Fomenko.