Author SHA1 Message Date
Brian Bjarke Jensen 9e5e5b58c7 added env vars 2025-11-12 18:23:35 +01:00
9 changed files with 22 additions and 221 deletions
@@ -6,7 +6,7 @@ import logging
from baby_monitor.repositories.interfaces import (
TokenRepositoryInterface,
)
from baby_monitor.repositories.token import (
from baby_monitor.repositories.token.memory_token import (
InMemoryTokenRepository,
)
@@ -43,7 +43,7 @@ def _initialize_token_repository() -> TokenRepositoryInterface:
) from e
try:
from baby_monitor.repositories.token import (
from baby_monitor.repositories.token.redis_token import (
RedisTokenRepository,
)
@@ -1,11 +0,0 @@
"""Token repository implementations and configurations."""
from .default_ttl import DEFAULT_TTL
from .memory_token import InMemoryTokenRepository
from .redis_token import RedisTokenRepository
__all__ = [
"DEFAULT_TTL",
"InMemoryTokenRepository",
"RedisTokenRepository",
]
@@ -1,3 +0,0 @@
"""Definition of Token default TTL."""
DEFAULT_TTL = 28800 # seconds -> 8 hours
@@ -3,7 +3,6 @@
from datetime import datetime, timedelta, UTC
from baby_monitor.repositories.interfaces import TokenRepositoryInterface
from baby_monitor.repositories.token import DEFAULT_TTL
class InMemoryTokenRepository(TokenRepositoryInterface):
@@ -17,8 +16,9 @@ class InMemoryTokenRepository(TokenRepositoryInterface):
def __init__(self) -> None:
# token -> (user_id, expiry_time)
self._tokens: dict[str, tuple[int, datetime]] = {}
self._default_ttl = 3600 # Store default TTL for sliding expiration
def store(self, token: str, user_id: int, ttl: int = DEFAULT_TTL) -> None:
def store(self, token: str, user_id: int, ttl: int = 3600) -> None:
"""Store a token with TTL (time to live in seconds)."""
expiry = datetime.now(UTC) + timedelta(seconds=ttl)
self._tokens[token] = (user_id, expiry)
@@ -39,7 +39,7 @@ class InMemoryTokenRepository(TokenRepositoryInterface):
return None
# Sliding expiration: extend the token lifetime
new_expiry = datetime.now(UTC) + timedelta(seconds=DEFAULT_TTL)
new_expiry = datetime.now(UTC) + timedelta(seconds=self._default_ttl)
self._tokens[token] = (user_id, new_expiry)
return user_id
@@ -3,14 +3,14 @@
from typing import Any
from baby_monitor.repositories.interfaces import TokenRepositoryInterface
from baby_monitor.repositories.token import DEFAULT_TTL
class RedisTokenRepository(TokenRepositoryInterface):
"""
Redis-based token storage.
This implementation uses Redis to store tokens with TTL. Implements sliding expiration.
Requires redis package: pip install redis
Set REDIS_URI environment variable (e.g., redis://localhost:6379/0)
"""
def __init__(self, redis_client: Any) -> None:
@@ -21,8 +21,9 @@ class RedisTokenRepository(TokenRepositoryInterface):
redis_client: Redis client instance from redis.from_url()
"""
self.redis = redis_client
self.default_ttl = 3600 # Store default TTL for sliding expiration
def store(self, token: str, user_id: int, ttl: int = DEFAULT_TTL) -> None:
def store(self, token: str, user_id: int, ttl: int = 3600) -> None:
"""Store a token with TTL (time to live in seconds)."""
key = f"token:{token}"
self.redis.setex(key, ttl, str(user_id))
@@ -36,7 +37,7 @@ class RedisTokenRepository(TokenRepositoryInterface):
user_id_str = self.redis.get(key)
if user_id_str:
# Sliding expiration: extend the token lifetime
self.redis.expire(key, DEFAULT_TTL)
self.redis.expire(key, self.default_ttl)
return int(user_id_str)
return None
+3 -21
View File
@@ -281,9 +281,6 @@
let currentDaysRange =
parseInt(localStorage.getItem("lastDiaperTimeRange")) || 7;
let selectedChildId = null; // null means show first/only child
let barChartInstance = null; // Track chart instance for proper cleanup
let poopPieChartInstance = null;
let peePieChartInstance = null;
// Restore last selected child from localStorage
const lastChildId = localStorage.getItem("lastDiaperChildFilter");
@@ -785,12 +782,7 @@
function renderBarChart(chartData) {
const ctx = document.getElementById("diaperChart").getContext("2d");
// Destroy existing chart instance if it exists
if (barChartInstance) {
barChartInstance.destroy();
}
barChartInstance = new Chart(ctx, {
new Chart(ctx, {
type: "bar",
data: {
labels: chartData.labels,
@@ -883,11 +875,6 @@
function renderPoopPieChart(chartData) {
const ctx = document.getElementById("poopPieChart").getContext("2d");
// Destroy existing chart instance if it exists
if (poopPieChartInstance) {
poopPieChartInstance.destroy();
}
const colors = {
Light: {
bg: "rgba(139, 69, 19, 0.5)",
@@ -914,7 +901,7 @@
(label) => colors[label]?.border || "rgba(200, 200, 200, 1)",
);
poopPieChartInstance = new Chart(ctx, {
new Chart(ctx, {
type: "pie",
data: {
labels: chartData.labels,
@@ -970,11 +957,6 @@
function renderPeePieChart(chartData) {
const ctx = document.getElementById("peePieChart").getContext("2d");
// Destroy existing chart instance if it exists
if (peePieChartInstance) {
peePieChartInstance.destroy();
}
const colors = {
Light: {
bg: "rgba(255, 215, 0, 0.5)",
@@ -1001,7 +983,7 @@
(label) => colors[label]?.border || "rgba(200, 200, 200, 1)",
);
peePieChartInstance = new Chart(ctx, {
new Chart(ctx, {
type: "pie",
data: {
labels: chartData.labels,
+2 -14
View File
@@ -319,8 +319,6 @@
parseInt(localStorage.getItem("lastFeedingTimeRange")) || 7;
let selectedChildId = null; // null means show first/only child
let barChartInstance = null;
let pieChartInstance = null;
let durationChartInstance = null;
// Restore last selected child from localStorage
const lastChildId = localStorage.getItem("lastFeedingChildFilter");
@@ -981,11 +979,6 @@
function renderPieChart(chartData) {
const ctx = document.getElementById("pieChart").getContext("2d");
// Destroy existing chart instance if it exists
if (pieChartInstance) {
pieChartInstance.destroy();
}
// Color mapping for each type
const colorMap = {
"Left Breast": {
@@ -1010,7 +1003,7 @@
(label) => colorMap[label]?.border || "rgba(200, 200, 200, 1)",
);
pieChartInstance = new Chart(ctx, {
new Chart(ctx, {
type: "pie",
data: {
labels: chartData.labels,
@@ -1066,12 +1059,7 @@
function renderDurationChart(chartData) {
const ctx = document.getElementById("durationChart").getContext("2d");
// Destroy existing chart instance if it exists
if (durationChartInstance) {
durationChartInstance.destroy();
}
durationChartInstance = new Chart(ctx, {
new Chart(ctx, {
type: "bar",
data: {
labels: chartData.labels,
-150
View File
@@ -167,52 +167,6 @@
margin-top: 0.5rem;
opacity: 0.9;
}
.recent-activity {
margin-top: 2rem;
padding: 1.5rem;
background: #f8f9fa;
border-radius: 8px;
}
.recent-activity h3 {
margin-top: 0;
margin-bottom: 1rem;
color: #333;
font-size: 1.1rem;
}
.activity-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.activity-card {
background: white;
padding: 1rem;
border-radius: 6px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.activity-card .icon {
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
.activity-card .type {
font-weight: 600;
color: #333;
margin-bottom: 0.25rem;
}
.activity-card .time {
font-size: 0.85rem;
color: #666;
}
.activity-card .details {
font-size: 0.85rem;
color: #888;
margin-top: 0.5rem;
}
.activity-card.empty {
opacity: 0.5;
font-style: italic;
color: #999;
}
</style>
</head>
<body>
@@ -367,7 +321,6 @@
let activeSleep = null;
let lastSleep = null;
let sleepUpdateTimerInterval = null;
let lastDiaperChange = null;
async function loadFeedingStatus(children) {
userChildren = children;
@@ -423,20 +376,6 @@
lastSleep = sleeps[0];
}
}
// Fetch all diaper changes to get the last one
const diaperResponse = await fetch("/api/diaper-changes", {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (diaperResponse.ok) {
const diapers = await diaperResponse.json();
if (diapers.length > 0) {
lastDiaperChange = diapers[0];
}
}
} catch (error) {
console.error("Error loading feeding status:", error);
}
@@ -539,98 +478,9 @@
</button>
${feedingButtonHtml}
${sleepButtonHtml}
${buildRecentActivitySection()}
`;
}
function buildRecentActivitySection() {
// Build diaper card
const diaperCard = lastDiaperChange
? `
<div class="activity-card">
<div class="icon">🧷</div>
<div class="type">Last Diaper</div>
<div class="time">${formatTime(lastDiaperChange.change_time)}</div>
<div class="details">
${lastDiaperChange.poop_amount ? `💩 ${lastDiaperChange.poop_amount}` : ""}
${lastDiaperChange.poop_amount && lastDiaperChange.pee_amount ? " • " : ""}
${lastDiaperChange.pee_amount ? `💧 ${lastDiaperChange.pee_amount}` : ""}
</div>
</div>
`
: `
<div class="activity-card empty">
<div class="icon">🧷</div>
<div class="type">No diaper changes yet</div>
</div>
`;
// Build feeding card
const feedingCard = lastFeeding
? `
<div class="activity-card">
<div class="icon">🍼</div>
<div class="type">Last Feeding</div>
<div class="time">${formatTime(lastFeeding.start_time)}</div>
<div class="details">
${formatFeedingType(lastFeeding.feeding_type)}
${lastFeeding.end_time ? `${calculateDuration(lastFeeding.start_time, lastFeeding.end_time)}` : " (ongoing)"}
</div>
</div>
`
: `
<div class="activity-card empty">
<div class="icon">🍼</div>
<div class="type">No feedings yet</div>
</div>
`;
// Build sleep card
const sleepCard = lastSleep
? `
<div class="activity-card">
<div class="icon">😴</div>
<div class="type">Last Sleep</div>
<div class="time">${formatTime(lastSleep.start_time)}</div>
<div class="details">
${lastSleep.end_time ? calculateDuration(lastSleep.start_time, lastSleep.end_time) : "ongoing"}
</div>
</div>
`
: `
<div class="activity-card empty">
<div class="icon">😴</div>
<div class="type">No sleep sessions yet</div>
</div>
`;
return `
<div class="recent-activity">
<h3>📊 Recent Activity</h3>
<div class="activity-grid">
${diaperCard}
${feedingCard}
${sleepCard}
</div>
</div>
`;
}
function calculateDuration(startTime, endTime) {
const start = new Date(startTime);
const end = new Date(endTime);
const diffMs = end - start;
const diffMins = Math.floor(diffMs / 60000);
if (diffMins < 60) {
return `${diffMins} min`;
}
const hours = Math.floor(diffMins / 60);
const mins = diffMins % 60;
return `${hours}h ${mins}m`;
}
function formatFeedingType(type) {
const types = {
left_breast: "Left Breast",
+4 -10
View File
@@ -345,16 +345,13 @@
}
}
function attachTimeRangeListener() {
const timeRangeFilter = document.getElementById("timeRangeFilter");
if (timeRangeFilter) {
timeRangeFilter.addEventListener("change", function () {
document
.getElementById("timeRangeFilter")
.addEventListener("change", function () {
// Save selection to localStorage
localStorage.setItem("lastSleepTimeRange", this.value);
renderCharts();
loadData();
});
}
}
async function loadData() {
try {
@@ -446,9 +443,6 @@
attachChildFilterListener();
}
// Always attach time range listener after recreating the controls
attachTimeRangeListener();
renderCharts();
} catch (error) {
console.error("Error loading data:", error);