Skip to content

fire0clop/medoed

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

27 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

medoed banner

Medoed

A diabetes companion for iOS β€” carb counting, insulin bolus estimation, a shared dish library, and a meal diary, backed by a secure FastAPI service.

Swift SwiftUI iOS Python FastAPI SQLAlchemy PostgreSQL JWT License: MIT

⚠️ 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.


πŸ“‹ Overview

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).


✨ Features

  • 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.

πŸ— Architecture

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
Loading

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.


🧰 Tech Stack

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
Email smtplib over SSL
Config .env via pydantic-settings

πŸ“ Project Structure

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

πŸš€ Getting Started

Backend

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 8000

Tables are created automatically on startup via Base.metadata.create_all. Interactive API docs are then available at http://localhost:8000/docs.

iOS App

Requires Xcode 15+ and iOS 16+.

cd frontend
open app.xcodeproj

Then:

  1. Set your own signing team and bundle identifier in the project settings.
  2. Point the client at your backend by editing baseURL in app/Utils/Constants.swift.
  3. For Google Sign-In, set the reversed-client-id URL scheme in Info.plist to your own Google iOS OAuth client.
  4. Build and run on a simulator or device.

πŸ”Œ API

Base URL depends on your deployment. All bodies are JSON; protected routes require Authorization: Bearer <access_token>.

Auth β€” /auth

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

Profile β€” /profile

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

Dishes β€” /dishes

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)

Meals β€” /meals

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.


πŸ—Ί Status / Roadmap

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

πŸ“„ License

Released under the MIT License. Β© 2026 Egor Fomenko.

About

iOS + FastAPI diabetes app: insulin bolus calculator, carb-counting dish library, and meal diary with JWT auth.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors