Architecture by Deepseek - jbrucker/home-log GitHub Wiki
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:
-
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
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)
- Simple JWT-based auth (or session cookies for web)
- Each user only accesses their own homes/data
-
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
- 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)
flowchart LR
Mobile -->|API Calls| Backend
Web -->|API Calls| Backend
Backend --> Database
-
Time-Series Optimization: If recording frequent readings (e.g., every 15 mins), consider
- Database partitioning by time
- Down-sampling old data
-
Offline Support (Mobile)
- Local SQLite storage with sync capability
-
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)?
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:
flowchart TD
A[Android App] --> B[Backend API]
C[Web App] --> B
B --> D[(Database)]
-
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).
-
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).
-
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.
-
-
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.
- Add a
last_sync_time
column to theReadings
table to help resolve conflicts if offline edits occur. - Consider SQLite on Android and PostgreSQL on server with similar schemas.
If you foresee:
- Adding iOS later → Keep React Native.
- Complex analytics → Keep Django/PostgreSQL.
- Web app as primary interface → No change.
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 |
-
Offline Support:
- Design the Android app to store readings locally first, then sync in background.
- Use Android’s
WorkManager
for reliable sync retries.
-
Auth:
- Firebase Auth (simple) or JWT tokens from your backend.
-
API Example (Django REST Framework):
# views.py class ReadingViewSet(viewsets.ModelViewSet): queryset = Reading.objects.all() serializer_class = ReadingSerializer filterset_fields = ['source_id', 'timestamp']
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)]
-
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 yourUsers
table instead of emails.
-
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).
- Add a LINE Login callback endpoint (e.g.,
-
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.
-
Database:
- No schema changes needed, but ensure
Users
table has aline_user_id
column.
- No schema changes needed, but ensure
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) |
-
Set Up LINE Developer Account:
- Register your MiniApp at LINE Developers Console.
- Get
Channel ID
andChannel Secret
for OAuth.
-
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)})
-
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 });
-
API Security:
- Validate LINE’s ID tokens on every request if sensitive data is accessed.
- 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.
If you want to skip backend development:
- Use Firebase Auth with LINE Login integration.
- Store data in Firestore with security rules like:
match /homes/{homeId} { allow read, write: if request.auth != null && resource.data.owner == request.auth.uid; }
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?
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]
Install the required packages:
pip install fastapi python-multipart httpx python-jose[cryptography] # For JWT/OAuth
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
...
- Add a
line_user_id
column to yourUsers
table:ALTER TABLE users ADD COLUMN line_user_id VARCHAR(64) UNIQUE;
- 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
- 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 | 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 |
Component | Technology |
---|---|
Backend | FastAPI + SQLAlchemy |
Auth | LINE Login + JWT |
Database | PostgreSQL |
Android | Kotlin + Room |
LINE MiniApp | LIFF SDK + React |
-
Containerize FastAPI:
FROM tiangolo/uvicorn-gunicorn-fastapi COPY ./app /app
-
Reverse Proxy (NGINX):
location / { proxy_pass http://fastapi:8000; }
- 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?
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):
pip install fastapi python-jose[cryptography] httpx python-multipart "uvicorn[standard]"
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"]
}
// After LINE Login, send token with "LINE " prefix
fetch("https://your-api.com/protected-data", {
headers: {
"Authorization": "LINE <LINE_ACCESS_TOKEN>"
}
});
// After traditional login, send JWT
fetch("https://your-api.com/protected-data", {
headers: {
"Authorization": "Bearer <JWT_TOKEN>"
}
});
-
Dual Auth Support:
- Detects
LINE <token>
orBearer <jwt>
automatically. - Validates against LINE's API or local JWT secret.
- Detects
-
Security:
- Uses HTTPS-only cookies (adjust
HTTPBearer
as needed). - LINE tokens are validated in real-time (no caching).
- Uses HTTPS-only cookies (adjust
-
Extensible:
- Add more auth providers (e.g., Google) by extending the middleware.
- 401 Unauthorized for invalid/missing tokens.
-
403 Forbidden can be added for role-based checks (e.g.,
user.is_admin
).
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
- 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?