Compare commits
4 Commits
da76baa2ae
...
v1.0.9
| Author | SHA1 | Date | |
|---|---|---|---|
| 5348bedb86 | |||
| 5bdc37ebcf | |||
| e042943bd3 | |||
| 1e705b822a |
@@ -53,6 +53,7 @@ jobs:
|
||||
--platform linux/arm64 \
|
||||
--build-arg VITE_APP_VERSION=$(git describe --tags) \
|
||||
--build-arg VITE_APP_GITSHA=$(git rev-parse --short HEAD) \
|
||||
--build-arg VITE_BASE_PATH=/api/ingress/timer \
|
||||
-t gitea.lan/jacobh2/babymonitor-frontend:${{ env.VERSION }} \
|
||||
./frontend \
|
||||
--push
|
||||
@@ -60,4 +61,3 @@ jobs:
|
||||
run: |
|
||||
git tag "$VERSION"
|
||||
git push origin "$VERSION"
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
"""add indexes to sleep_session
|
||||
|
||||
Revision ID: a3282e5bef40
|
||||
Revises: 7a424a341b1d
|
||||
Create Date: 2026-06-12 22:55:33.711961
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'a3282e5bef40'
|
||||
down_revision: Union[str, Sequence[str], None] = '7a424a341b1d'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_index(op.f('ix_sleep_session_end_time'), 'sleep_session', ['end_time'], unique=False)
|
||||
op.create_index(op.f('ix_sleep_session_start_time'), 'sleep_session', ['start_time'], unique=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(op.f('ix_sleep_session_start_time'), table_name='sleep_session')
|
||||
op.drop_index(op.f('ix_sleep_session_end_time'), table_name='sleep_session')
|
||||
# ### end Alembic commands ###
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
import os
|
||||
from fastapi import FastAPI
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
@@ -12,7 +13,7 @@ async def lifespan(app: FastAPI):
|
||||
yield
|
||||
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
app = FastAPI(lifespan=lifespan, root_path=os.getenv("ROOT_PATH", ""))
|
||||
|
||||
# CORS middleware
|
||||
app.add_middleware(
|
||||
|
||||
@@ -7,7 +7,7 @@ class SleepSession(SQLModel, table=True):
|
||||
__tablename__ = "sleep_session"
|
||||
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
start_time: datetime = Field(nullable=False)
|
||||
end_time: Optional[datetime] = Field(default=None, nullable=True)
|
||||
start_time: datetime = Field(index=True, nullable=False)
|
||||
end_time: Optional[datetime] = Field(default=None, nullable=True, index=True)
|
||||
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
||||
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
||||
|
||||
@@ -8,12 +8,15 @@ services:
|
||||
environment:
|
||||
- DATABASE_URL=sqlite+aiosqlite:///./data/sleep.db
|
||||
- CORS_ORIGINS=http://localhost
|
||||
- ROOT_PATH=/api/ingress/timer
|
||||
restart: unless-stopped
|
||||
|
||||
frontend:
|
||||
build:
|
||||
context: ./frontend
|
||||
platforms: [linux/arm64]
|
||||
args:
|
||||
- VITE_BASE_PATH=/api/ingress/timer
|
||||
ports:
|
||||
- "8080:80"
|
||||
depends_on:
|
||||
|
||||
@@ -2,8 +2,10 @@ FROM node:22-alpine AS build
|
||||
|
||||
ARG VITE_APP_VERSION=unknown
|
||||
ARG VITE_APP_GITSHA=unknown
|
||||
ARG VITE_BASE_PATH
|
||||
ENV VITE_APP_VERSION=$VITE_APP_VERSION
|
||||
ENV VITE_APP_GITSHA=$VITE_APP_GITSHA
|
||||
ENV VITE_BASE_PATH=$VITE_BASE_PATH
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files first to leverage Docker cache
|
||||
|
||||
@@ -9,7 +9,7 @@ import Footer from './components/Footer';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Router>
|
||||
<Router basename={import.meta.env.BASE_URL}>
|
||||
<div className="min-h-screen bg-[#f8fafc] text-slate-900 font-sans antialiased">
|
||||
<Navbar />
|
||||
<main className="container mx-auto px-4 sm:px-6 lg:px-8 mt-8">
|
||||
|
||||
@@ -9,7 +9,8 @@ import type {
|
||||
SummaryResponse
|
||||
} from '@/types';
|
||||
|
||||
const BASE_URL = import.meta.env.VITE_API_BASE_URL || '';
|
||||
const API_BASE = import.meta.env.BASE_URL.replace(/\/$/, '')
|
||||
export const apiUrl = (path: string) => `${API_BASE}${path}`
|
||||
|
||||
/**
|
||||
* Convert a local HH:MM time string to its UTC equivalent.
|
||||
@@ -33,7 +34,7 @@ export function localToUTC(localValue: string): string {
|
||||
|
||||
async function fetchStats<T>(endpoint: string, from_date: string, to_date: string, extraParams: Record<string, string> = {}): Promise<T> {
|
||||
const params = new URLSearchParams({ from_date, to_date, ...extraParams });
|
||||
const response = await fetch(`${BASE_URL}/api/stats/${endpoint}?${params.toString()}`);
|
||||
const response = await fetch(apiUrl(`/api/stats/${endpoint}?${params.toString()}`));
|
||||
if (!response.ok) throw new Error(`Failed to fetch ${endpoint}`);
|
||||
return response.json();
|
||||
}
|
||||
@@ -60,19 +61,19 @@ export async function fetchWakeSleepTimes(from_date: string, to_date: string, wa
|
||||
|
||||
// Session API functions
|
||||
export async function fetchActiveSession(): Promise<SleepSessionRead | null> {
|
||||
const response = await fetch(`${BASE_URL}/api/sessions/active`);
|
||||
const response = await fetch(apiUrl('/api/sessions/active'));
|
||||
if (!response.ok) return null;
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function fetchSessions(): Promise<SessionListResponse> {
|
||||
const response = await fetch(`${BASE_URL}/api/sessions`);
|
||||
const response = await fetch(apiUrl('/api/sessions'));
|
||||
if (!response.ok) throw new Error('Failed to fetch sessions');
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function createSession(payload: SleepSessionCreate): Promise<SleepSessionRead> {
|
||||
const response = await fetch(`${BASE_URL}/api/sessions`, {
|
||||
const response = await fetch(apiUrl('/api/sessions'), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
@@ -82,7 +83,7 @@ export async function createSession(payload: SleepSessionCreate): Promise<SleepS
|
||||
}
|
||||
|
||||
export async function endSession(sessionId: number, payload: SleepSessionUpdate): Promise<SleepSessionRead> {
|
||||
const response = await fetch(`${BASE_URL}/api/sessions/${sessionId}/end`, {
|
||||
const response = await fetch(apiUrl(`/api/sessions/${sessionId}/end`), {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
@@ -92,14 +93,14 @@ export async function endSession(sessionId: number, payload: SleepSessionUpdate)
|
||||
}
|
||||
|
||||
export async function deleteSession(sessionId: number): Promise<void> {
|
||||
const response = await fetch(`${BASE_URL}/api/sessions/${sessionId}`, {
|
||||
const response = await fetch(apiUrl(`/api/sessions/${sessionId}`), {
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (!response.ok) throw new Error('Failed to delete session');
|
||||
}
|
||||
|
||||
export async function updateSession(sessionId: number, payload: SleepSessionUpdate): Promise<SleepSessionRead> {
|
||||
const response = await fetch(`${BASE_URL}/api/sessions/${sessionId}`, {
|
||||
const response = await fetch(apiUrl(`/api/sessions/${sessionId}`), {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
|
||||
@@ -19,7 +19,7 @@ if (isDev) {
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
base: process.env.VITE_BASE_PATH || '/',
|
||||
plugins: [react(), tailwindcss()],
|
||||
define: {
|
||||
__APP_VERSION__: JSON.stringify(APP_VERSION),
|
||||
|
||||
Reference in New Issue
Block a user