Architecture by Deepseek - jbrucker/home-log GitHub Wiki

Deepseek Recommended Architecture

For your residential data recording application, you'll want a simple but scalable architecture that supports both web and mobile interfaces while handling time-series data efficiently. Here is a recommended approach:

1. Client-Server Architecture with REST API Backend

  • Frontend:
    • Web: React/Vue.js (SPA) or traditional server-rendered (Django, Laravel)
    • Mobile: React Native/Flutter (cross-platform) or native Swift/Kotlin
  • Backend:
    • Language: Python (Django/Flask), Node.js, or Go
    • API: REST or GraphQL endpoints
  • Database:
    • PostgreSQL (good for relational data + JSON support)
    • OR Time-series database like InfluxDB if you expect heavy metrics storage

2. Database Design

Minimal tables needed:

Users (id, email, password_hash)
Homes (id, user_id, name, address)
DataSources (id, home_id, type [electric/water/gas], unit)
Readings (id, source_id, value, timestamp, notes)

3. Authentication

  • Simple JWT-based auth (or session cookies for web)
  • Each user only accesses their own homes/data

4. Deployment

  • Option A: Monolithic (simplest for few users)
    • Single server running backend + web frontend
    • Mobile apps connect to same API
    • Host on Heroku/DigitalOcean/AWS Lightsail
  • Option B: Microservices (if you anticipate scaling)
    • Separate services for API, auth, data ingestion

5. Recommended Tech Stack (Balancing Simplicity & Flexibility)

  • Frontend: React (web) + React Native (mobile) - reuse logic
  • Backend: Django (batteries-included) or Express.js (lighter)
  • Database: PostgreSQL (with TimescaleDB extension if time-series grows)
  • Auth: Firebase Auth (if you want ready-made auth)

6. Data Flow

flowchart LR
  Mobile -->|API Calls| Backend
  Web -->|API Calls| Backend
  Backend --> Database
Loading

Key Considerations

  1. Time-Series Optimization: If recording frequent readings (e.g., every 15 mins), consider

    • Database partitioning by time
    • Down-sampling old data
  2. Offline Support (Mobile)

    • Local SQLite storage with sync capability
  3. Hosting Cost

    • Start with a single $5-10/month VPS for low user count

This architecture gives you

  • Clear separation of concerns
  • Easy expansion to new features (e.g., analytics, sharing)
  • Low maintenance overhead for few users

Would you like me to elaborate on any specific part (e.g., database schema details, API endpoints)?


Android-First Architecture

I followed-up the question by asking:

The mobile interface will be implemented only on Android. Does this change your suggested architecture?

The Android-only focus simplifies some aspects of your architecture while keeping the core structure intact. Here are the key adjustments and optimizations:

Revised Architecture (Android-First)

flowchart TD
    A[Android App] --> B[Backend API]
    C[Web App] --> B
    B --> D[(Database)]
Loading

Key Changes & Optimizations

  1. Mobile Development:

    • Use Kotlin (Native Android) instead of React Native/Flutter since cross-platform isn’t needed.
    • Jetpack Compose for modern UI with less boilerplate.
    • Room Database for offline-first support (sync later when online).
  2. Backend Simplifications:

    • If all users will primarily use Android, you could even start with Firebase (Auth + Firestore) to skip backend development entirely.
    • Otherwise, keep Django/Node.js but optimize APIs for Android’s needs:
      • Batch uploads (e.g., send a week of readings at once).
      • Efficient polling/WebSockets for real-time updates (if needed).
  3. Android-Specific API Considerations:

    • Use Protocol Buffers (protobuf) instead of JSON for smaller payloads if bandwidth is a concern.
    • Endpoint examples:
      • POST /api/readings – Accept arrays of readings for batch upload.
      • GET /api/homes/{id}/readings?from=...&to=... – Time-range queries.
  4. Deployment:

    • If using Firebase: No server management needed.
    • If using a backend: Stick with a single VPS (e.g., Ubuntu + Docker) since user count is low.

Database Tweaks

  • Add a last_sync_time column to the Readings table to help resolve conflicts if offline edits occur.
  • Consider SQLite on Android and PostgreSQL on server with similar schemas.

When to Stick with Original Plan

If you foresee:

  • Adding iOS later → Keep React Native.
  • Complex analytics → Keep Django/PostgreSQL.
  • Web app as primary interface → No change.

Recommended Stack (Android-Centric)

Component Technology Choices
Android Kotlin + Jetpack Compose + Room
Backend Firebase or Django/Node.js
Database Firestore or PostgreSQL
Web React (if kept simple) or Django HTML

Implementation Tips

  1. Offline Support:

    • Design the Android app to store readings locally first, then sync in background.
    • Use Android’s WorkManager for reliable sync retries.
  2. Auth:

    • Firebase Auth (simple) or JWT tokens from your backend.
  3. API Example (Django REST Framework):

    # views.py
    class ReadingViewSet(viewsets.ModelViewSet):
        queryset = Reading.objects.all()
        serializer_class = ReadingSerializer
        filterset_fields = ['source_id', 'timestamp']

Architecture including LINE MiniApp Support

Integrating LINE MiniApp (a lightweight app within the LINE messenger) into your architecture is feasible with minor adjustments. Here’s how to adapt the system while keeping it simple:

flowchart TD
    LINE_MiniApp -->|API Calls| Backend
    Android_App -->|API Calls| Backend
    Web -->|API Calls| Backend
    Backend --> Database[(PostgreSQL/Firestore)]
Loading

Key Changes for LINE MiniApp Compatibility

  1. Authentication:

    • Replace traditional email/password auth with LINE Login (OAuth 2.0).
    • Users authenticate via their LINE accounts, and your backend verifies tokens from LINE’s API.
    • Store LINE user IDs (userId) in your Users table instead of emails.
  2. Backend Adjustments:

    • Add a LINE Login callback endpoint (e.g., /api/auth/line/callback).
    • Modify existing APIs to accept LINE’s userId instead of internal user IDs (or map them internally).
  3. LINE MiniApp-Specific Optimizations:

    • Lightweight API responses: LINE MiniApps run in a restricted environment, so minimize payload size (use pagination for readings).
    • No offline support: Unlike native Android, MiniApps can’t store data locally persistently.
  4. Database:

    • No schema changes needed, but ensure Users table has a line_user_id column.

Modified Tech Stack Recommendations

Component Technology Choices
LINE MiniApp LINE’s JavaScript SDK + React/Vue for UI
Backend Django/Node.js with LINE Login SDK (e.g., line-login for Node.js)
Auth LINE Login (primary), keep Firebase/JWT as fallback for Android/Web if needed
Database Unchanged (PostgreSQL or Firestore)

Implementation Steps

  1. Set Up LINE Developer Account:

  2. Backend Auth Flow:

    # Django Example (simplified)
    from linebot import LineBotApi
    
    def line_login_callback(request):
        code = request.GET.get('code')
        line_bot_api = LineBotApi('YOUR_CHANNEL_ACCESS_TOKEN')
        profile = line_bot_api.get_profile(code)
        user, _ = User.objects.get_or_create(line_user_id=profile.user_id)
        return JsonResponse({'token': generate_jwt(user)})
  3. MiniApp Frontend (JavaScript):

    // Initialize LINE SDK
    liff.init({ liffId: 'YOUR_LIFF_ID' })
      .then(() => {
        if (!liff.isLoggedIn()) liff.login();
        const lineUserId = liff.getContext().userId;
        // Send userId to your backend to get readings
      });
  4. API Security:

    • Validate LINE’s ID tokens on every request if sensitive data is accessed.

Architecture Notes

  • Shared APIs: The same backend endpoints serve Android, Web, and LINE MiniApp (just differ in auth).
  • Data Isolation: Ensure all queries filter by line_user_id (e.g., GET /readings returns only the current user’s data).
  • Limitations:
    • LINE MiniApps can’t run background tasks (sync must be triggered manually).
    • No push notifications unless you use LINE’s messaging API.

When to Use Firebase

If you want to skip backend development:

  1. Use Firebase Auth with LINE Login integration.
  2. Store data in Firestore with security rules like:
    match /homes/{homeId} {
      allow read, write: if request.auth != null && resource.data.owner == request.auth.uid;
    }

Suggested Changes Summary

Original Component Adjustment for LINE MiniApp
Auth System Replace with LINE Login
APIs Add line_user_id to queries
Android App Unchanged (can keep separate auth if needed)
Web Unchanged

Would you like me to draft a sample LINE MiniApp UI or the security rules for Firestore?


FastAPI for Backend Web Service

For the backend can I use FastAPI, yet still use the LINE Login SDK?

Yes, you can absolutely use FastAPI for the backend while integrating LINE Login SDK. FastAPI’s async support and simplicity make it a great choice for this use case. Here’s how to adapt the architecture:

flowchart TD
    LINE_MiniApp -->|API Calls| FastAPI
    Android_App -->|API Calls| FastAPI
    Web -->|API Calls| FastAPI
    FastAPI --> Database[(PostgreSQL/Firestore)]
    FastAPI --> LINE_API[LINE Login API]
Loading

Key Implementation Steps

1. Set Up LINE Login in FastAPI

Install the required packages:

pip install fastapi python-multipart httpx python-jose[cryptography]  # For JWT/OAuth

2. FastAPI Auth Endpoint for LINE Login

from fastapi import FastAPI, Request, HTTPException
from fastapi.security import OAuth2AuthorizationCodeBearer
import httpx

app = FastAPI()

# LINE OAuth2 config (from LINE Developers Console)
LINE_CLIENT_ID = "YOUR_CHANNEL_ID"
LINE_CLIENT_SECRET = "YOUR_CHANNEL_SECRET"
LINE_REDIRECT_URI = "https://yourbackend.com/auth/line/callback"
LINE_TOKEN_URL = "https://api.line.me/oauth2/v2.1/token"
LINE_PROFILE_URL = "https://api.line.me/v2/profile"

@app.get("/auth/line/callback")
async def line_callback(code: str):
    # 1. Exchange code for access token
    async with httpx.AsyncClient() as client:
        token_response = await client.post(
            LINE_TOKEN_URL,
            data={
                "grant_type": "authorization_code",
                "code": code,
                "redirect_uri": LINE_REDIRECT_URI,
                "client_id": LINE_CLIENT_ID,
                "client_secret": LINE_CLIENT_SECRET,
            },
        )
        token_data = token_response.json()
        
        # 2. Get user profile from LINE
        profile_response = await client.get(
            LINE_PROFILE_URL,
            headers={"Authorization": f"Bearer {token_data['access_token']}"},
        )
        profile = profile_response.json()
        
        # 3. Create or fetch user in your DB
        user = await get_or_create_user(profile["userId"])
        
        # 4. Return a JWT token for your API
        return {"token": create_jwt(user.id)}

# Helper functions (pseudo-code)
async def get_or_create_user(line_user_id: str):
    # Check DB and return user or create new
    ...

def create_jwt(user_id: str):
    # Generate a JWT token for your API
    ...

Required Adjustments to Your Architecture

1. Database Schema

  • Add a line_user_id column to your Users table:
    ALTER TABLE users ADD COLUMN line_user_id VARCHAR(64) UNIQUE;

2. API Security

  • Protect endpoints using FastAPI’s OAuth2PasswordBearer (for Android/Web) and LINE token validation (for MiniApp):
    from fastapi import Depends, HTTPException
    from fastapi.security import OAuth2PasswordBearer
    
    oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")
    
    async def get_current_user(token: str = Depends(oauth2_scheme)):
        # Validate JWT or LINE token
        if is_line_token(token):
            return validate_line_token(token)  # Call LINE API
        else:
            return decode_jwt(token)  # Your existing JWT logic

3. LINE MiniApp Frontend

  • Initialize LIFF and pass the LINE access token to FastAPI:
    liff.init({ liffId: "YOUR_LIFF_ID" }).then(() => {
      const lineToken = liff.getAccessToken();
      fetch("https://yourbackend.com/api/readings", {
        headers: { "Authorization": `Bearer ${lineToken}` }
      });
    });

Pros and Cons of FastAPI for This Use Case

Pros Cons
Async support (efficient for LINE API calls) No built-in admin panel (unlike Django)
Easy JWT integration Less "batteries-included" than Django
Python ecosystem (SQLAlchemy, Pydantic) LINE SDKs are less mature than Node.js

Full Stack Recommendation

Component Technology
Backend FastAPI + SQLAlchemy
Auth LINE Login + JWT
Database PostgreSQL
Android Kotlin + Room
LINE MiniApp LIFF SDK + React

Deployment Notes

  1. Containerize FastAPI:
    FROM tiangolo/uvicorn-gunicorn-fastapi
    COPY ./app /app
  2. Reverse Proxy (NGINX):
    location / {
        proxy_pass http://fastapi:8000;
    }

Alternatives

  • If you prefer Node.js: Use line-login SDK with Express.js (more LINE examples available).
  • If you need Firebase: Replace FastAPI with Firebase Functions + Firestore.

Would you like a detailed FastAPI middleware example for handling both LINE and JWT auth? Or a sample Android-to-FastAPI communication flow?


Detailed FastAPI middleware example to handle both LINE and JWT authentication

Here's a detailed FastAPI middleware example that handles both LINE Login and traditional JWT authentication in a single pipeline, ensuring secure access for your web/mobile apps (JWT) and LINE MiniApp users (LINE tokens):


1. Setup: Install Dependencies

pip install fastapi python-jose[cryptography] httpx python-multipart "uvicorn[standard]"

2. Auth Middleware (Complete Example)

from fastapi import FastAPI, Depends, HTTPException, Request, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from jose import JWTError, jwt
from pydantic import BaseModel
import httpx
from typing import Optional

# Config (store these securely in environment variables)
SECRET_KEY = "your-secret-key"  # For JWT signing
ALGORITHM = "HS256"
LINE_CLIENT_ID = "YOUR_LINE_CHANNEL_ID"
LINE_ISSUER = "https://access.line.me"

app = FastAPI()
security = HTTPBearer()

# --- Models ---
class User(BaseModel):
    id: str
    line_user_id: Optional[str] = None  # Only for LINE users

# --- LINE Token Validation ---
async def verify_line_token(token: str) -> User:
    try:
        # LINE's token introspection endpoint
        async with httpx.AsyncClient() as client:
            response = await client.get(
                "https://api.line.me/oauth2/v2.1/verify",
                params={"access_token": token}
            )
            if response.status_code != 200:
                raise HTTPException(status_code=401, detail="Invalid LINE token")
            
            line_profile = response.json()
            if line_profile.get("client_id") != LINE_CLIENT_ID:
                raise HTTPException(status_code=401, detail="Token audience mismatch")
            
            return User(id=line_profile["sub"], line_user_id=line_profile["sub"])
    except Exception as e:
        raise HTTPException(status_code=401, detail=f"LINE token verification failed: {str(e)}")

# --- JWT Validation ---
async def verify_jwt(token: str) -> User:
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        user_id = payload.get("sub")
        if not user_id:
            raise HTTPException(status_code=401, detail="Invalid JWT payload")
        return User(id=user_id)
    except JWTError as e:
        raise HTTPException(status_code=401, detail=f"JWT validation failed: {str(e)}")

# --- Unified Auth Middleware ---
async def get_current_user(
    credentials: HTTPAuthorizationCredentials = Depends(security),
    request: Request
) -> User:
    token = credentials.credentials
    
    # Check if token is from LINE (starts with "LINE ") or is a JWT
    if token.startswith("LINE "):
        line_token = token.split(" ")[1]
        return await verify_line_token(line_token)
    else:
        return await verify_jwt(token)

# --- Protected Route Example ---
@app.get("/protected-data")
async def protected_data(user: User = Depends(get_current_user)):
    return {
        "message": f"Hello, {user.id} (LINE: {user.line_user_id or 'N/A'})",
        "data": ["your", "protected", "items"]
    }

3. Client-Side Usage

For LINE MiniApp (JavaScript):

// After LINE Login, send token with "LINE " prefix
fetch("https://your-api.com/protected-data", {
  headers: {
    "Authorization": "LINE <LINE_ACCESS_TOKEN>"
  }
});

For Web/Android (JWT):

// After traditional login, send JWT
fetch("https://your-api.com/protected-data", {
  headers: {
    "Authorization": "Bearer <JWT_TOKEN>"
  }
});

4. Key Features

  1. Dual Auth Support:

    • Detects LINE <token> or Bearer <jwt> automatically.
    • Validates against LINE's API or local JWT secret.
  2. Security:

    • Uses HTTPS-only cookies (adjust HTTPBearer as needed).
    • LINE tokens are validated in real-time (no caching).
  3. Extensible:

    • Add more auth providers (e.g., Google) by extending the middleware.

5. Error Handling

  • 401 Unauthorized for invalid/missing tokens.
  • 403 Forbidden can be added for role-based checks (e.g., user.is_admin).

6. Testing

Test with curl:

# JWT Test
curl -H "Authorization: Bearer YOUR_JWT" http://localhost:8000/protected-data

# LINE Test
curl -H "Authorization: LINE YOUR_LINE_TOKEN" http://localhost:8000/protected-data

7. Optimization Tips

  • Cache LINE token validation (e.g., Redis) to reduce API calls.
  • Use FastAPI's BackgroundTasks for non-blocking LINE validation.
  • Add rate limiting (e.g., slowapi) to prevent abuse.

This middleware keeps your architecture clean while supporting both authentication flows. Would you like me to extend it with role-based access control (RBAC) or SQLAlchemy user fetching?

⚠️ **GitHub.com Fallback** ⚠️