Commit 8605ff94 authored by Hendrik Garske's avatar Hendrik Garske

feat: Automatische Datenbank-Initialisierung ohne .env Datei - Default-Config für Development

parent 0ac1b018
Pipeline #26 passed with stages
in 1 minute and 40 seconds
...@@ -95,33 +95,11 @@ yarn install ...@@ -95,33 +95,11 @@ yarn install
pnpm install pnpm install
``` ```
### 3. Umgebungsvariablen konfigurieren ### 3. Datenbank Setup
Erstellen Sie eine `.env` Datei im Root-Verzeichnis:
```bash
cp .env.example .env
```
**WICHTIG**: Bearbeiten Sie die `.env` Datei und setzen Sie die `DATABASE_URL` mit Ihren PostgreSQL-Daten:
```env
DATABASE_URL="postgresql://username:password@localhost:5432/corex_dashboard?schema=public"
NODE_ENV="development"
NEXT_TELEMETRY_DISABLED=1
```
**Für Production/Deployment**: Stellen Sie sicher, dass `DATABASE_URL` in Ihrer Deployment-Umgebung (z.B. CapRover) als Environment Variable gesetzt ist.
### 4. Datenbank Setup
Siehe [Datenbank Setup](#datenbank-setup) für detaillierte Anweisungen. Siehe [Datenbank Setup](#datenbank-setup) für detaillierte Anweisungen.
### 4. Datenbank Setup ### 4. Development Server starten
Siehe [Datenbank Setup](#datenbank-setup) für detaillierte Anweisungen.
### 5. Development Server starten
```bash ```bash
npm run dev npm run dev
...@@ -129,21 +107,27 @@ npm run dev ...@@ -129,21 +107,27 @@ npm run dev
Die Anwendung läuft jetzt auf [http://localhost:3000](http://localhost:3000) Die Anwendung läuft jetzt auf [http://localhost:3000](http://localhost:3000)
## ⚙️ Konfiguration ## 🗄️ Datenbank Setup
### Umgebungsvariablen Die Anwendung konfiguriert sich automatisch. Die Datenbank-Tabellen werden beim ersten Start automatisch erstellt.
| Variable | Beschreibung | Beispiel | ### Automatische Konfiguration
|----------|--------------|----------|
| `DATABASE_URL` | PostgreSQL Verbindungs-URL | `postgresql://user:pass@localhost:5432/dbname` |
### Datenbank-URL Format - **Default-Datenbank**: `postgresql://postgres:postgres@localhost:5432/corex_dashboard`
- **Tabellen**: Werden automatisch beim ersten API-Aufruf erstellt
- **Keine .env Datei erforderlich** für lokale Entwicklung
``` ### Manuelle Konfiguration (Optional)
postgresql://[user]:[password]@[host]:[port]/[database]?schema=public
Falls Sie eine andere Datenbank verwenden möchten, können Sie optional eine `.env` Datei erstellen:
```env
DATABASE_URL="postgresql://username:password@host:port/database?schema=public"
``` ```
## 🗄️ Datenbank Setup ### Production Deployment
Für Production stellen Sie `DATABASE_URL` in Ihrer Deployment-Umgebung (z.B. CapRover) als Environment Variable ein.
### PostgreSQL Installation (Linux) ### PostgreSQL Installation (Linux)
...@@ -459,7 +443,6 @@ FROM base AS builder ...@@ -459,7 +443,6 @@ FROM base AS builder
WORKDIR /app WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/node_modules ./node_modules
COPY . . COPY . .
RUN npx prisma generate
RUN npm run build RUN npm run build
# Production image, copy all the files and run next # Production image, copy all the files and run next
......
import { NextRequest, NextResponse } from "next/server" import { NextRequest, NextResponse } from "next/server"
import { query } from "@/lib/db" import { query, initDatabase } from "@/lib/db"
import { rowToCustomer } from "@/lib/utils/db-helpers" import { rowToCustomer } from "@/lib/utils/db-helpers"
export async function GET( export async function GET(
request: NextRequest, request: NextRequest,
{ params }: { params: Promise<{ id: string }> } { params }: { params: Promise<{ id: string }> }
) { ) {
// Ensure database is initialized
await initDatabase()
try { try {
const { id } = await params const { id } = await params
...@@ -72,6 +75,9 @@ export async function PUT( ...@@ -72,6 +75,9 @@ export async function PUT(
request: NextRequest, request: NextRequest,
{ params }: { params: Promise<{ id: string }> } { params }: { params: Promise<{ id: string }> }
) { ) {
// Ensure database is initialized
await initDatabase()
try { try {
const { id } = await params const { id } = await params
const body = await request.json() const body = await request.json()
...@@ -114,6 +120,9 @@ export async function DELETE( ...@@ -114,6 +120,9 @@ export async function DELETE(
request: NextRequest, request: NextRequest,
{ params }: { params: Promise<{ id: string }> } { params }: { params: Promise<{ id: string }> }
) { ) {
// Ensure database is initialized
await initDatabase()
try { try {
const { id } = await params const { id } = await params
......
import { NextRequest, NextResponse } from "next/server" import { NextRequest, NextResponse } from "next/server"
import { query } from "@/lib/db" import { query, initDatabase } from "@/lib/db"
import { generateId, rowToCustomer } from "@/lib/utils/db-helpers" import { generateId, rowToCustomer } from "@/lib/utils/db-helpers"
export async function GET(request: NextRequest) { export async function GET(request: NextRequest) {
// Ensure database is initialized
await initDatabase()
try { try {
const searchParams = request.nextUrl.searchParams const searchParams = request.nextUrl.searchParams
const search = searchParams.get("search") const search = searchParams.get("search")
...@@ -74,6 +76,9 @@ export async function GET(request: NextRequest) { ...@@ -74,6 +76,9 @@ export async function GET(request: NextRequest) {
} }
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
// Ensure database is initialized
await initDatabase()
try { try {
const body = await request.json() const body = await request.json()
const { name, email, phone, company, address, notes } = body const { name, email, phone, company, address, notes } = body
......
import { NextResponse } from "next/server"
import { initDatabase } from "@/lib/db"
export async function GET() {
try {
await initDatabase()
return NextResponse.json({ success: true, message: "Database initialized" })
} catch (error) {
console.error("Error initializing database:", error)
const errorMessage = error instanceof Error ? error.message : String(error)
return NextResponse.json(
{ success: false, error: errorMessage },
{ status: 500 }
)
}
}
import { NextRequest, NextResponse } from "next/server" import { NextRequest, NextResponse } from "next/server"
import { query } from "@/lib/db" import { query, initDatabase } from "@/lib/db"
export async function DELETE( export async function DELETE(
request: NextRequest, request: NextRequest,
{ params }: { params: Promise<{ id: string }> } { params }: { params: Promise<{ id: string }> }
) { ) {
// Ensure database is initialized
await initDatabase()
try { try {
const { id } = await params const { id } = await params
......
import { NextRequest, NextResponse } from "next/server" import { NextRequest, NextResponse } from "next/server"
import { query } from "@/lib/db" import { query, initDatabase } from "@/lib/db"
import { generateId, rowToTimeEntry } from "@/lib/utils/db-helpers" import { generateId, rowToTimeEntry } from "@/lib/utils/db-helpers"
export async function GET(request: NextRequest) { export async function GET(request: NextRequest) {
// Ensure database is initialized
await initDatabase()
try { try {
const searchParams = request.nextUrl.searchParams const searchParams = request.nextUrl.searchParams
const customerId = searchParams.get("customerId") const customerId = searchParams.get("customerId")
...@@ -74,6 +76,9 @@ export async function GET(request: NextRequest) { ...@@ -74,6 +76,9 @@ export async function GET(request: NextRequest) {
} }
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
// Ensure database is initialized
await initDatabase()
try { try {
const body = await request.json() const body = await request.json()
const { description, startTime, endTime, duration, customerId, userId } = body const { description, startTime, endTime, duration, customerId, userId } = body
......
import { NextRequest, NextResponse } from "next/server" import { NextRequest, NextResponse } from "next/server"
import { query } from "@/lib/db" import { query, initDatabase } from "@/lib/db"
import { generateId, rowToUser } from "@/lib/utils/db-helpers" import { generateId, rowToUser } from "@/lib/utils/db-helpers"
export async function GET() { export async function GET() {
// Ensure database is initialized
await initDatabase()
try { try {
const result = await query( const result = await query(
`SELECT * FROM users ORDER BY "createdAt" DESC` `SELECT * FROM users ORDER BY "createdAt" DESC`
...@@ -17,6 +19,9 @@ export async function GET() { ...@@ -17,6 +19,9 @@ export async function GET() {
} }
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
// Ensure database is initialized
await initDatabase()
try { try {
const body = await request.json() const body = await request.json()
const { email, name, image } = body const { email, name, image } = body
......
import { initDatabase } from './db'
// Initialize database on module load (server-side only)
if (typeof window === 'undefined') {
initDatabase().catch((error) => {
console.error('Failed to initialize database on startup:', error)
})
}
...@@ -2,24 +2,28 @@ import { Pool } from 'pg' ...@@ -2,24 +2,28 @@ import { Pool } from 'pg'
let pool: Pool | undefined let pool: Pool | undefined
// Default database configuration for development
const DEFAULT_DATABASE_URL = 'postgresql://postgres:postgres@localhost:5432/corex_dashboard?schema=public'
// Lazy initialization of the database pool // Lazy initialization of the database pool
function getPool(): Pool { function getPool(): Pool {
if (!pool) { if (!pool) {
const connectionString = process.env.DATABASE_URL const connectionString = process.env.DATABASE_URL || DEFAULT_DATABASE_URL
if (!connectionString) {
throw new Error('DATABASE_URL environment variable is not set')
}
pool = new Pool({ pool = new Pool({
connectionString, connectionString,
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false, ssl: process.env.NODE_ENV === 'production' && process.env.DATABASE_URL?.includes('sslmode=require')
? { rejectUnauthorized: false }
: false,
}) })
// Handle pool errors // Handle pool errors
pool.on('error', (err) => { pool.on('error', (err) => {
console.error('Unexpected error on idle client', err) console.error('Unexpected error on idle client', err)
// Don't exit in development, just log
if (process.env.NODE_ENV === 'production') {
process.exit(-1) process.exit(-1)
}
}) })
} }
...@@ -40,3 +44,120 @@ export async function query(text: string, params?: unknown[]) { ...@@ -40,3 +44,120 @@ export async function query(text: string, params?: unknown[]) {
return res return res
} }
// Initialize database tables if they don't exist
export async function initDatabase() {
try {
const dbPool = getPool()
// Check if tables exist
const result = await dbPool.query(`
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'customers'
);
`)
if (!result.rows[0].exists) {
console.log('Initializing database tables...')
// Read and execute init SQL
const fs = await import('fs/promises')
const path = await import('path')
try {
const sqlPath = path.join(process.cwd(), 'scripts', 'init-db.sql')
const sql = await fs.readFile(sqlPath, 'utf-8')
// Split by semicolon and execute each statement
const statements = sql
.split(';')
.map(s => s.trim())
.filter(s => s.length > 0 && !s.startsWith('--'))
for (const statement of statements) {
if (statement.trim()) {
await dbPool.query(statement)
}
}
console.log('Database tables initialized successfully')
} catch (fileError) {
console.error('Error reading init-db.sql, trying direct SQL...', fileError)
// Fallback: execute SQL directly
await dbPool.query(`
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
image TEXT,
"createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS customers (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
email TEXT,
phone TEXT,
company TEXT,
address TEXT,
notes TEXT,
"createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS time_entries (
id TEXT PRIMARY KEY,
description TEXT NOT NULL,
"startTime" TIMESTAMP NOT NULL,
"endTime" TIMESTAMP NOT NULL,
duration INTEGER NOT NULL,
"createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
"customerId" TEXT NOT NULL REFERENCES customers(id) ON DELETE CASCADE ON UPDATE CASCADE,
"userId" TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_time_entries_customer_id ON time_entries("customerId");
CREATE INDEX IF NOT EXISTS idx_time_entries_user_id ON time_entries("userId");
CREATE INDEX IF NOT EXISTS idx_time_entries_created_at ON time_entries("createdAt");
`)
// Create trigger function and triggers
await dbPool.query(`
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW."updatedAt" = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ language 'plpgsql';
`)
await dbPool.query(`
DROP TRIGGER IF EXISTS update_users_updated_at ON users;
CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
`)
await dbPool.query(`
DROP TRIGGER IF EXISTS update_customers_updated_at ON customers;
CREATE TRIGGER update_customers_updated_at BEFORE UPDATE ON customers
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
`)
await dbPool.query(`
DROP TRIGGER IF EXISTS update_time_entries_updated_at ON time_entries;
CREATE TRIGGER update_time_entries_updated_at BEFORE UPDATE ON time_entries
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
`)
console.log('Database tables initialized successfully (fallback method)')
}
}
} catch (error) {
console.error('Error initializing database:', error)
// Don't throw - allow app to continue, database might not be available yet
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment