feat: add theme system, branding, and task logging

- Add light/dark/system theme toggle in settings
- Add DearDiary.io branding in navbar
- Add task logging for journal generation with request/response
- Rename project from TotalRecall to DearDiary
- Update Docker configuration
This commit is contained in:
lotherk
2026-03-26 20:03:52 +00:00
parent 3f9bc1f484
commit a4e7132244
28 changed files with 487 additions and 47 deletions

47
CHANGELOG.md Normal file
View File

@@ -0,0 +1,47 @@
# Changelog
All notable changes to DearDiary will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
### Added
- **Task System**: AI journal generation now creates tasks that track:
- Request sent to AI provider (full prompt + config)
- Response received from AI
- Status: pending, completed, failed
- Error messages if failed
- `Task` model in database for logging
- `GET /api/v1/journal/:date/tasks` endpoint
- `GET /api/v1/tasks/:id` endpoint
- **Theme System**: Light/Dark/System theme toggle
- **Branding**: "DearDiary.io" logo in navbar
### Changed
- **Renamed project from "TotalRecall" to "DearDiary"**
- Journal generation now returns `{ journal, task }` on success
- Auth redirect now works properly (PrivateRoute component)
- Android app package: `com.totalrecall``com.deardiary`
### Fixed
- Ollama support: properly routes to configured baseUrl
- Anthropic API integration
## [0.1.0] - 2026-03-26
### Added
- User authentication (register/login)
- API key authentication for app access
- Entry CRUD (text, voice, health types)
- Day aggregation and history
- Journal generation with multiple AI providers:
- OpenAI (GPT-4)
- Anthropic (Claude)
- Ollama (local)
- LM Studio (local)
- Settings page for AI configuration
- React frontend with dark theme
- Native Android app (Kotlin/Compose)
- Docker deployment
- Prisma ORM with SQLite (extensible to PostgreSQL/MySQL)

46
Dockerfile Normal file
View File

@@ -0,0 +1,46 @@
# Multi-stage build: Backend + Frontend
FROM oven/bun:1.1-alpine AS backend-builder
WORKDIR /app/backend
COPY backend/package*.json ./
RUN bun install
COPY backend/prisma ./prisma
RUN bunx prisma generate
COPY backend/src ./src
RUN bun build src/index.ts --outdir ./dist --target bun
FROM node:20-alpine AS frontend-builder
WORKDIR /app/frontend
COPY frontend/package*.json ./
RUN npm install
COPY frontend ./
RUN npm run build
FROM oven/bun:1.1-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs || true
# Install nginx for serving frontend
RUN apk add --no-cache nginx
# Copy backend
COPY --from=backend-builder /app/backend/dist ./dist
COPY --from=backend-builder /app/backend/node_modules ./node_modules
COPY backend/package.json .
COPY backend/prisma ./prisma
# Copy frontend build
COPY --from=frontend-builder /app/frontend/dist ./public
# Setup nginx
COPY nginx.conf /etc/nginx/http.d/default.conf
RUN mkdir -p /data /run /app/nginx_tmp /var/lib/nginx/logs && chmod 777 /var/lib/nginx/logs /var/lib/nginx/tmp && chown -R bun:nodejs /data /app
# Start everything as root
CMD sh -c "nginx -g 'daemon off;' & bunx prisma db push --accept-data-loss & bun ./dist/index.js"

View File

@@ -4,6 +4,8 @@
AI-powered daily journal that captures life through multiple input methods and generates thoughtful, reflective journal entries.
See [CHANGELOG.md](./CHANGELOG.md) for detailed version history.
## Features
- **Multiple Input Types**: Text notes, photos, voice memos, health data

View File

@@ -4,11 +4,11 @@ plugins {
}
android {
namespace = "com.totalrecall"
namespace = "com.deardiary"
compileSdk = 34
defaultConfig {
applicationId = "com.totalrecall"
applicationId = "com.deardiary"
minSdk = 26
targetSdk = 34
versionCode = 1

View File

@@ -18,12 +18,12 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.TotalRecall"
android:theme="@style/Theme.DearDiary"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.TotalRecall">
android:theme="@style/Theme.DearDiary">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

View File

@@ -1,4 +1,4 @@
package com.totalrecall
package com.deardiary
import android.os.Bundle
import androidx.activity.ComponentActivity
@@ -8,8 +8,8 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import com.totalrecall.ui.AppNavigation
import com.totalrecall.ui.TotalRecallTheme
import com.deardiary.ui.AppNavigation
import com.deardiary.ui.TotalRecallTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {

View File

@@ -1,4 +1,4 @@
package com.totalrecall.api
package com.deardiary.api
import com.google.gson.Gson
import com.google.gson.annotations.SerializedName

View File

@@ -1,4 +1,4 @@
package com.totalrecall.repository
package com.deardiary.repository
import android.content.Context
import androidx.datastore.core.DataStore
@@ -6,7 +6,7 @@ import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.totalrecall.api.*
import com.deardiary.api.*
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "totalrecall")

View File

@@ -1,4 +1,4 @@
package com.totalrecall.ui
package com.deardiary.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
@@ -10,12 +10,12 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.totalrecall.ui.auth.AuthScreen
import com.totalrecall.ui.history.HistoryScreen
import com.totalrecall.ui.home.HomeScreen
import com.totalrecall.ui.journal.JournalScreen
import com.totalrecall.ui.settings.SettingsScreen
import com.totalrecall.viewmodel.MainViewModel
import com.deardiary.ui.auth.AuthScreen
import com.deardiary.ui.history.HistoryScreen
import com.deardiary.ui.home.HomeScreen
import com.deardiary.ui.journal.JournalScreen
import com.deardiary.ui.settings.SettingsScreen
import com.deardiary.viewmodel.MainViewModel
import kotlinx.coroutines.launch
sealed class Screen(val route: String) {

View File

@@ -1,4 +1,4 @@
package com.totalrecall.ui
package com.deardiary.ui
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.*

View File

@@ -1,4 +1,4 @@
package com.totalrecall.ui.auth
package com.deardiary.ui.auth
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
@@ -9,7 +9,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import com.totalrecall.viewmodel.UiState
import com.deardiary.viewmodel.UiState
@Composable
fun AuthScreen(

View File

@@ -1,4 +1,4 @@
package com.totalrecall.ui.history
package com.deardiary.ui.history
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
@@ -12,7 +12,7 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.totalrecall.api.DayInfo
import com.deardiary.api.DayInfo
import java.text.SimpleDateFormat
import java.util.*

View File

@@ -1,4 +1,4 @@
package com.totalrecall.ui.home
package com.deardiary.ui.home
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
@@ -11,8 +11,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.totalrecall.api.DayResponse
import com.totalrecall.api.Entry
import com.deardiary.api.DayResponse
import com.deardiary.api.Entry
import java.text.SimpleDateFormat
import java.util.*

View File

@@ -1,4 +1,4 @@
package com.totalrecall.ui.journal
package com.deardiary.ui.journal
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
@@ -12,7 +12,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.totalrecall.api.Journal
import com.deardiary.api.Journal
import java.text.SimpleDateFormat
import java.util.*

View File

@@ -1,4 +1,4 @@
package com.totalrecall.ui.settings
package com.deardiary.ui.settings
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
@@ -14,7 +14,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import com.totalrecall.api.Settings
import com.deardiary.api.Settings
@OptIn(ExperimentalMaterial3Api::class)
@Composable

View File

@@ -1,11 +1,11 @@
package com.totalrecall.viewmodel
package com.deardiary.viewmodel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.totalrecall.BuildConfig
import com.totalrecall.api.*
import com.totalrecall.repository.Repository
import com.deardiary.BuildConfig
import com.deardiary.api.*
import com.deardiary.repository.Repository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">TotalRecall</string>
<string name="app_name">DearDiary</string>
</resources>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.TotalRecall" parent="android:Theme.Material.Light.NoActionBar">
<style name="Theme.DearDiary" parent="android:Theme.Material.Light.NoActionBar">
<item name="android:statusBarColor">@color/black</item>
</style>
</resources>

24
docker-compose.yml Normal file
View File

@@ -0,0 +1,24 @@
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
- "5173:80"
environment:
- DATABASE_URL=file:/data/deardiary.db
- MEDIA_DIR=/data/media
- JWT_SECRET=${JWT_SECRET:-change-me-in-production}
- PORT=3000
- CORS_ORIGIN=${CORS_ORIGIN:-*}
volumes:
- ./data:/data
restart: unless-stopped
extra_hosts:
- "host.docker.internal:host-gateway"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3

View File

@@ -58,11 +58,18 @@ function App() {
<div className="min-h-screen bg-slate-950 text-slate-100">
{isAuthenticated && (
<nav className="bg-slate-900 border-b border-slate-800 sticky top-0 z-50">
<div className="max-w-4xl mx-auto px-4 py-3 flex gap-6">
<div className="max-w-4xl mx-auto px-4 py-3 flex items-center justify-between">
<a href="/" className="flex items-center gap-2">
<span className="text-lg font-bold bg-gradient-to-r from-purple-400 to-pink-400 bg-clip-text text-transparent">
DearDiary.io
</span>
</a>
<div className="flex gap-6">
<a href="/" className="text-slate-300 hover:text-white transition">Today</a>
<a href="/history" className="text-slate-300 hover:text-white transition">History</a>
<a href="/settings" className="text-slate-300 hover:text-white transition">Settings</a>
</div>
</div>
</nav>
)}
<Routes>

View File

@@ -2,6 +2,74 @@
@tailwind components;
@tailwind utilities;
body {
@apply bg-slate-950 text-slate-100;
:root {
--bg-primary: #020917;
--bg-secondary: #0f172a;
--bg-tertiary: #1e293b;
--text-primary: #e2e8f0;
--text-secondary: #94a3b8;
--border-color: #334155;
}
.light {
--bg-primary: #ffffff;
--bg-secondary: #f8fafc;
--bg-tertiary: #f1f5f9;
--text-primary: #1e293b;
--text-secondary: #64748b;
--border-color: #e2e8f0;
}
body {
background-color: var(--bg-primary);
color: var(--text-primary);
transition: background-color 0.2s, color 0.2s;
}
.bg-slate-950 { background-color: var(--bg-primary); }
.bg-slate-900 { background-color: var(--bg-secondary); }
.bg-slate-800 { background-color: var(--bg-tertiary); }
.bg-slate-700 { background-color: #475569; }
.text-slate-100 { color: var(--text-primary); }
.text-slate-200 { color: var(--text-primary); }
.text-slate-300 { color: var(--text-primary); }
.text-slate-400 { color: var(--text-secondary); }
.text-slate-500 { color: var(--text-secondary); }
.border-slate-800 { border-color: var(--border-color); }
.border-slate-700 { border-color: var(--border-color); }
.light .bg-slate-900 { background-color: #f1f5f9; }
.light .bg-slate-800 { background-color: #e2e8f0; }
.light .bg-slate-700 { background-color: #cbd5e1; }
.light .text-slate-300 { color: #475569; }
.light .text-slate-400 { color: #64748b; }
.light .border-slate-800 { border-color: #e2e8f0; }
.bg-white { background-color: var(--bg-primary); }
.bg-black { background-color: var(--bg-secondary); }
.ring-offset-slate-950 { --tw-ring-offset-color: var(--bg-primary); }
.dark .ring-offset-slate-950 { --tw-ring-offset-color: #020917; }
.light .ring-offset-slate-950 { --tw-ring-offset-color: #ffffff; }
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--bg-secondary);
}
::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-secondary);
}

View File

@@ -0,0 +1,83 @@
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
type Theme = 'light' | 'dark' | 'system';
type ResolvedTheme = 'light' | 'dark';
interface ThemeContextType {
theme: Theme;
resolvedTheme: ResolvedTheme;
setTheme: (theme: Theme) => void;
}
const ThemeContext = createContext<ThemeContextType>({
theme: 'system',
resolvedTheme: 'dark',
setTheme: () => {},
});
export function useTheme() {
return useContext(ThemeContext);
}
function getSystemTheme(): ResolvedTheme {
if (typeof window !== 'undefined' && window.matchMedia) {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
return 'dark';
}
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setThemeState] = useState<Theme>(() => {
if (typeof window !== 'undefined') {
return (localStorage.getItem('theme') as Theme) || 'system';
}
return 'system';
});
const [resolvedTheme, setResolvedTheme] = useState<ResolvedTheme>(() => {
if (theme === 'system') {
return getSystemTheme();
}
return theme;
});
useEffect(() => {
const root = document.documentElement;
if (theme === 'system') {
const systemTheme = getSystemTheme();
setResolvedTheme(systemTheme);
root.classList.remove('light', 'dark');
root.classList.add(systemTheme);
} else {
setResolvedTheme(theme);
root.classList.remove('light', 'dark');
root.classList.add(theme);
}
}, [theme]);
useEffect(() => {
if (theme === 'system') {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handler = (e: MediaQueryListEvent) => {
const newTheme = e.matches ? 'dark' : 'light';
setResolvedTheme(newTheme);
document.documentElement.classList.remove('light', 'dark');
document.documentElement.classList.add(newTheme);
};
mediaQuery.addEventListener('change', handler);
return () => mediaQuery.removeEventListener('change', handler);
}
}, [theme]);
const setTheme = (newTheme: Theme) => {
setThemeState(newTheme);
localStorage.setItem('theme', newTheme);
};
return (
<ThemeContext.Provider value={{ theme, resolvedTheme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}

View File

@@ -126,13 +126,21 @@ class ApiClient {
}
async generateJournal(date: string) {
return this.request<Journal>('POST', `/journal/generate/${date}`);
return this.request<{ journal: Journal; task: Task }>('POST', `/journal/generate/${date}`);
}
async getJournal(date: string) {
return this.request<Journal>('GET', `/journal/${date}`);
}
async getJournalTasks(date: string) {
return this.request<Task[]>('GET', `/journal/${date}/tasks`);
}
async getTask(taskId: string) {
return this.request<Task>('GET', `/tasks/${taskId}`);
}
async getSettings() {
return this.request<Settings>('GET', '/settings');
}
@@ -160,6 +168,21 @@ export interface Journal {
generatedAt: string;
}
export interface Task {
id: string;
journalId: string;
type: string;
status: 'pending' | 'completed' | 'failed';
provider: string;
model?: string;
prompt?: string;
request?: string;
response?: string;
error?: string;
createdAt: string;
completedAt?: string;
}
export interface Settings {
aiProvider: 'openai' | 'anthropic' | 'ollama' | 'lmstudio';
aiApiKey?: string;

View File

@@ -1,10 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { ThemeProvider } from './lib/ThemeContext';
import './index.css';
const savedTheme = localStorage.getItem('theme') || 'system';
const isDark = savedTheme === 'dark' || (savedTheme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
document.documentElement.classList.add(isDark ? 'dark' : 'light');
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<ThemeProvider>
<App />
</ThemeProvider>
</React.StrictMode>,
);

View File

@@ -1,15 +1,19 @@
import { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { api, Journal } from '../lib/api';
import { api, Journal, Task } from '../lib/api';
export default function JournalPage() {
const { date } = useParams<{ date: string }>();
const [journal, setJournal] = useState<Journal | null>(null);
const [tasks, setTasks] = useState<Task[]>([]);
const [loading, setLoading] = useState(true);
const [generating, setGenerating] = useState(false);
useEffect(() => {
if (date) loadJournal();
if (date) {
loadJournal();
loadTasks();
}
}, [date]);
const loadJournal = async () => {
@@ -22,12 +26,21 @@ export default function JournalPage() {
setLoading(false);
};
const loadTasks = async () => {
if (!date) return;
const res = await api.getJournalTasks(date);
if (res.data) {
setTasks(res.data);
}
};
const handleGenerate = async () => {
if (!date) return;
setGenerating(true);
const res = await api.generateJournal(date);
if (res.data) {
setJournal(res.data);
setJournal(res.data.journal);
setTasks(prev => [res.data!.task, ...prev]);
}
setGenerating(false);
};
@@ -81,6 +94,63 @@ export default function JournalPage() {
</div>
</div>
)}
{tasks.length > 0 && (
<div className="mt-8">
<h2 className="text-lg font-medium mb-4">Generation History</h2>
<div className="space-y-3">
{tasks.map(task => (
<details key={task.id} className="bg-slate-900 rounded-lg border border-slate-800">
<summary className="p-4 cursor-pointer flex items-center justify-between">
<div className="flex items-center gap-3">
<span className={`w-2 h-2 rounded-full ${
task.status === 'completed' ? 'bg-green-500' :
task.status === 'failed' ? 'bg-red-500' : 'bg-yellow-500'
}`} />
<span className="font-medium">
{task.provider.charAt(0).toUpperCase() + task.provider.slice(1)}
{task.model && ` - ${task.model}`}
</span>
<span className="text-sm text-slate-400">
{new Date(task.createdAt).toLocaleString()}
</span>
</div>
<span className={`text-xs px-2 py-1 rounded ${
task.status === 'completed' ? 'bg-green-500/20 text-green-400' :
task.status === 'failed' ? 'bg-red-500/20 text-red-400' : 'bg-yellow-500/20 text-yellow-400'
}`}>
{task.status}
</span>
</summary>
<div className="px-4 pb-4 space-y-3">
{task.error && (
<div className="bg-red-500/10 border border-red-500/20 rounded p-3">
<p className="text-sm text-red-400 font-medium mb-1">Error:</p>
<pre className="text-sm text-red-300 whitespace-pre-wrap">{task.error}</pre>
</div>
)}
{task.request && (
<div>
<p className="text-sm text-slate-400 font-medium mb-1">Request:</p>
<pre className="bg-slate-950 rounded p-3 text-xs text-slate-300 overflow-x-auto max-h-64">
{JSON.stringify(JSON.parse(task.request), null, 2)}
</pre>
</div>
)}
{task.response && (
<div>
<p className="text-sm text-slate-400 font-medium mb-1">Response:</p>
<pre className="bg-slate-950 rounded p-3 text-xs text-slate-300 overflow-x-auto max-h-64">
{JSON.stringify(JSON.parse(task.response), null, 2)}
</pre>
</div>
)}
</div>
</details>
))}
</div>
</div>
)}
</div>
);
}

View File

@@ -1,11 +1,13 @@
import { useState, useEffect } from 'react';
import { api, Settings } from '../lib/api';
import { useTheme } from '../lib/ThemeContext';
export default function SettingsPage() {
const [settings, setSettings] = useState<Partial<Settings>>({});
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [saved, setSaved] = useState(false);
const { theme, setTheme } = useTheme();
useEffect(() => {
loadSettings();
@@ -47,6 +49,46 @@ export default function SettingsPage() {
<h1 className="text-2xl font-bold mb-6">Settings</h1>
<div className="space-y-6">
<section className="bg-slate-900 rounded-xl p-6 border border-slate-800">
<h2 className="text-lg font-medium mb-4">Appearance</h2>
<div className="space-y-3">
<label className="block text-sm text-slate-400 mb-2">Theme</label>
<div className="flex gap-2">
<button
onClick={() => setTheme('light')}
className={`flex-1 px-4 py-2 rounded-lg border transition ${
theme === 'light'
? 'bg-blue-600 border-blue-600 text-white'
: 'bg-slate-800 border-slate-700 hover:border-slate-600'
}`}
>
Light
</button>
<button
onClick={() => setTheme('dark')}
className={`flex-1 px-4 py-2 rounded-lg border transition ${
theme === 'dark'
? 'bg-blue-600 border-blue-600 text-white'
: 'bg-slate-800 border-slate-700 hover:border-slate-600'
}`}
>
Dark
</button>
<button
onClick={() => setTheme('system')}
className={`flex-1 px-4 py-2 rounded-lg border transition ${
theme === 'system'
? 'bg-blue-600 border-blue-600 text-white'
: 'bg-slate-800 border-slate-700 hover:border-slate-600'
}`}
>
System
</button>
</div>
</div>
</section>
<section className="bg-slate-900 rounded-xl p-6 border border-slate-800">
<h2 className="text-lg font-medium mb-4">AI Provider</h2>

View File

@@ -1 +1 @@
{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/EntryInput.tsx","./src/components/EntryList.tsx","./src/lib/api.ts","./src/pages/Auth.tsx","./src/pages/Day.tsx","./src/pages/History.tsx","./src/pages/Home.tsx","./src/pages/Journal.tsx","./src/pages/Settings.tsx"],"version":"5.9.3"}
{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/EntryInput.tsx","./src/components/EntryList.tsx","./src/lib/ThemeContext.tsx","./src/lib/api.ts","./src/pages/Auth.tsx","./src/pages/Day.tsx","./src/pages/History.tsx","./src/pages/Home.tsx","./src/pages/Journal.tsx","./src/pages/Settings.tsx"],"version":"5.9.3"}

21
nginx.conf Normal file
View File

@@ -0,0 +1,21 @@
server {
listen 80;
root /app/public;
index index.html;
client_body_temp_path /app/nginx_tmp/client_body;
proxy_temp_path /app/nginx_tmp/proxy;
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}