ADD: added dockerfile and docker-compose and k8s manifest
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
node_modules
|
||||||
|
build
|
||||||
|
.env
|
||||||
|
.git
|
||||||
|
*.md
|
||||||
|
src/
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
# ---- Build Stage ----
|
||||||
|
FROM node:22-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
COPY prisma ./prisma
|
||||||
|
RUN npx prisma generate
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# ---- Production Stage ----
|
||||||
|
FROM node:22-alpine AS runner
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm ci --omit=dev
|
||||||
|
|
||||||
|
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
|
||||||
|
COPY --from=builder /app/build ./build
|
||||||
|
COPY prisma ./prisma
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD ["npm", "run", "start"]
|
||||||
@@ -12,57 +12,100 @@ Buchhaltungs- und Rechnungsverwaltungssystem für Mandanten.
|
|||||||
## Tech Stack
|
## Tech Stack
|
||||||
|
|
||||||
- **React Router v7** (Framework Mode, SSR) + TypeScript
|
- **React Router v7** (Framework Mode, SSR) + TypeScript
|
||||||
- **MariaDB / MySQL** via Prisma ORM
|
- **MariaDB** via Prisma ORM
|
||||||
- **Cookie-Session-Auth** (bcryptjs, kein NextAuth)
|
- **Cookie-Session-Auth** (bcryptjs)
|
||||||
- **Tailwind CSS v4** + shadcn/ui
|
- **Tailwind CSS v4** + shadcn/ui
|
||||||
- **@react-pdf/renderer** für PDF-Generierung
|
- **@react-pdf/renderer** für PDF-Generierung
|
||||||
- **Docker** für die Datenbank
|
- **Docker** für die Datenbank
|
||||||
|
|
||||||
## Setup
|
## Lokale Entwicklung
|
||||||
|
|
||||||
### 1. Voraussetzungen
|
### Voraussetzungen
|
||||||
|
|
||||||
- Node.js 20+
|
- Node.js 22+
|
||||||
- Docker (für MariaDB)
|
- Docker + Docker Compose
|
||||||
|
|
||||||
### 2. Installation
|
### Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Umgebungsvariablen konfigurieren
|
### Umgebungsvariablen
|
||||||
|
|
||||||
```bash
|
`.env` im Projektstamm anlegen:
|
||||||
cp .env.example .env
|
|
||||||
# DATABASE_URL und AUTH_SECRET in .env anpassen
|
```env
|
||||||
|
DATABASE_URL="mysql://annas_user:annas_password@localhost:3306/annas_rechnungen"
|
||||||
|
AUTH_SECRET="dein-zufaelliger-secret-string"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Datenbank einrichten
|
### Datenbank einrichten
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx prisma migrate dev --name init
|
npm run db:migrate # Migrationen ausführen
|
||||||
npx prisma db seed
|
npm run db:seed # Demo-Daten einspielen
|
||||||
```
|
```
|
||||||
|
|
||||||
**Demo-Zugangsdaten:** `anna@example.de` / `demo123`
|
**Demo-Zugangsdaten:** `anna@example.de` / `demo123`
|
||||||
|
|
||||||
### 5. Entwicklungsserver starten
|
### Entwicklungsserver starten
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Startet Docker (PostgreSQL) und den Vite-Dev-Server.
|
Startet automatisch MariaDB via Docker und den Vite-Dev-Server auf `http://localhost:5173`.
|
||||||
|
|
||||||
Öffne [http://localhost:5173](http://localhost:5173)
|
## Scripts
|
||||||
|
|
||||||
## Datenbank-Kommandos
|
| Befehl | Beschreibung |
|
||||||
|
|---|---|
|
||||||
|
| `npm run dev` | DB starten + Dev-Server |
|
||||||
|
| `npm run build` | Produktions-Build |
|
||||||
|
| `npm run start` | Produktions-Server starten |
|
||||||
|
| `npm run typecheck` | TypeScript prüfen |
|
||||||
|
| `npm run db:migrate` | Prisma Migrationen ausführen |
|
||||||
|
| `npm run db:seed` | Demo-Daten einspielen |
|
||||||
|
| `npm run db:studio` | Prisma Studio öffnen |
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
### Docker Compose
|
||||||
|
|
||||||
|
Startet App + MariaDB als komplettes Setup:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run db:migrate # Migrationen ausführen
|
docker compose up -d
|
||||||
npm run db:seed # Demo-Daten einspielen
|
```
|
||||||
npm run db:studio # Prisma Studio öffnen
|
|
||||||
|
Die App läuft auf `http://localhost:3000`. Migrationen werden automatisch beim Start ausgeführt.
|
||||||
|
|
||||||
|
> `AUTH_SECRET` muss als Umgebungsvariable gesetzt sein.
|
||||||
|
|
||||||
|
### Kubernetes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Secret-Werte in k8s.yml anpassen (auth-secret, ggf. Passwörter), dann:
|
||||||
|
kubectl apply -f k8s.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
Das Manifest (`k8s.yml`) enthält: Namespace, Secret, MariaDB (PVC + Deployment + Service), App (Deployment mit Init-Container für Migrationen + Service + Ingress).
|
||||||
|
|
||||||
|
## Projektstruktur
|
||||||
|
|
||||||
|
```
|
||||||
|
app/
|
||||||
|
components/ UI-Komponenten (ui/, layout/, company/, invoice/)
|
||||||
|
lib/ Hilfsfunktionen (prisma, tax, utils, invoice-number)
|
||||||
|
routes/ Route-Dateien (React Router v7, file-based)
|
||||||
|
session.server.ts Auth (Login, Logout, Session)
|
||||||
|
types/ Gemeinsame TypeScript-Typen
|
||||||
|
db/
|
||||||
|
docker-compose.yml MariaDB + phpMyAdmin für lokale Entwicklung
|
||||||
|
prisma/
|
||||||
|
schema.prisma Datenbankschema
|
||||||
|
migrations/ Migrationsverlauf
|
||||||
```
|
```
|
||||||
|
|
||||||
## Rechnungs-Compliance (§14 UStG)
|
## Rechnungs-Compliance (§14 UStG)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useMatches, Link } from "react-router";
|
import { useMatches, useLocation, Link } from "react-router";
|
||||||
import { ChevronRight } from "lucide-react";
|
import { ChevronRight, LayoutDashboard } from "lucide-react";
|
||||||
|
|
||||||
interface Breadcrumb {
|
interface Breadcrumb {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -26,6 +26,8 @@ function getInitials(name?: string | null): string {
|
|||||||
|
|
||||||
export function Topbar({ userName }: { userName?: string | null }) {
|
export function Topbar({ userName }: { userName?: string | null }) {
|
||||||
const matches = useMatches();
|
const matches = useMatches();
|
||||||
|
const location = useLocation();
|
||||||
|
const isOnDashboard = location.pathname === "/";
|
||||||
|
|
||||||
const activeMatch = [...matches].reverse().find((m) => isBreadcrumbHandle(m.handle));
|
const activeMatch = [...matches].reverse().find((m) => isBreadcrumbHandle(m.handle));
|
||||||
const breadcrumbs: Breadcrumb[] =
|
const breadcrumbs: Breadcrumb[] =
|
||||||
@@ -83,6 +85,28 @@ export function Topbar({ userName }: { userName?: string | null }) {
|
|||||||
)}
|
)}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
{/* Dashboard Button */}
|
||||||
|
{!isOnDashboard && (
|
||||||
|
<Link
|
||||||
|
to="/"
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "0.375rem",
|
||||||
|
fontSize: "0.875rem",
|
||||||
|
color: "#64748b",
|
||||||
|
textDecoration: "none",
|
||||||
|
padding: "0.375rem 0.75rem",
|
||||||
|
borderRadius: "0.375rem",
|
||||||
|
border: "1px solid #e2e8f0",
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LayoutDashboard className="h-4 w-4" />
|
||||||
|
Dashboard
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* User */}
|
{/* User */}
|
||||||
{userName && (
|
{userName && (
|
||||||
<div style={{ display: "flex", alignItems: "center", gap: "0.625rem", marginLeft: "1rem", flexShrink: 0 }}>
|
<div style={{ display: "flex", alignItems: "center", gap: "0.625rem", marginLeft: "1rem", flexShrink: 0 }}>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ const companySchema = z.object({
|
|||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
legalForm: z.string().optional(),
|
legalForm: z.string().optional(),
|
||||||
taxId: z.string().optional(),
|
taxId: z.string().optional(),
|
||||||
vatId: z.string().optional(),
|
|
||||||
address: z.string().min(1),
|
address: z.string().min(1),
|
||||||
zip: z.string().min(1),
|
zip: z.string().min(1),
|
||||||
city: z.string().min(1),
|
city: z.string().min(1),
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { z } from "zod";
|
|||||||
|
|
||||||
const customerSchema = z.object({
|
const customerSchema = z.object({
|
||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
vatId: z.string().optional(),
|
|
||||||
taxId: z.string().optional(),
|
taxId: z.string().optional(),
|
||||||
address: z.string().min(1),
|
address: z.string().min(1),
|
||||||
zip: z.string().min(1),
|
zip: z.string().min(1),
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { z } from "zod";
|
|||||||
const customerSchema = z.object({
|
const customerSchema = z.object({
|
||||||
companyId: z.string().min(1),
|
companyId: z.string().min(1),
|
||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
vatId: z.string().optional(),
|
|
||||||
taxId: z.string().optional(),
|
taxId: z.string().optional(),
|
||||||
address: z.string().min(1),
|
address: z.string().min(1),
|
||||||
zip: z.string().min(1),
|
zip: z.string().min(1),
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
name: z.string().min(1, "Pflichtfeld"),
|
name: z.string().min(1, "Pflichtfeld"),
|
||||||
vatId: z.string().optional(),
|
// vatId: z.string().optional(),
|
||||||
address: z.string().min(1, "Pflichtfeld"),
|
address: z.string().min(1, "Pflichtfeld"),
|
||||||
zip: z.string().min(1, "Pflichtfeld"),
|
zip: z.string().min(1, "Pflichtfeld"),
|
||||||
city: z.string().min(1, "Pflichtfeld"),
|
city: z.string().min(1, "Pflichtfeld"),
|
||||||
@@ -35,7 +35,7 @@ type FormData = z.infer<typeof schema>;
|
|||||||
interface Customer {
|
interface Customer {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
vatId?: string | null;
|
// vatId?: string | null;
|
||||||
address: string;
|
address: string;
|
||||||
zip: string;
|
zip: string;
|
||||||
city: string;
|
city: string;
|
||||||
@@ -93,10 +93,10 @@ function CustomerForm({
|
|||||||
<Label>Ort *</Label>
|
<Label>Ort *</Label>
|
||||||
<Input {...register("city")} placeholder="Berlin" />
|
<Input {...register("city")} placeholder="Berlin" />
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1.5">
|
{/* <div className="space-y-1.5">
|
||||||
<Label>USt-IdNr.</Label>
|
<Label>USt-IdNr.</Label>
|
||||||
<Input {...register("vatId")} placeholder="DE..." />
|
<Input {...register("vatId")} placeholder="DE..." />
|
||||||
</div>
|
</div> */}
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<Label>E-Mail</Label>
|
<Label>E-Mail</Label>
|
||||||
<Input {...register("email")} type="email" placeholder="kontakt@..." />
|
<Input {...register("email")} type="email" placeholder="kontakt@..." />
|
||||||
@@ -185,7 +185,6 @@ export default function CustomersPage() {
|
|||||||
address: editCustomer.address,
|
address: editCustomer.address,
|
||||||
zip: editCustomer.zip,
|
zip: editCustomer.zip,
|
||||||
city: editCustomer.city,
|
city: editCustomer.city,
|
||||||
vatId: editCustomer.vatId ?? undefined,
|
|
||||||
email: editCustomer.email ?? undefined,
|
email: editCustomer.email ?? undefined,
|
||||||
phone: editCustomer.phone ?? undefined,
|
phone: editCustomer.phone ?? undefined,
|
||||||
}}
|
}}
|
||||||
@@ -212,7 +211,7 @@ export default function CustomersPage() {
|
|||||||
<div>
|
<div>
|
||||||
<p className="font-semibold text-gray-900">{customer.name}</p>
|
<p className="font-semibold text-gray-900">{customer.name}</p>
|
||||||
<p className="text-sm text-gray-500 mt-0.5">{customer.address}, {customer.zip} {customer.city}</p>
|
<p className="text-sm text-gray-500 mt-0.5">{customer.address}, {customer.zip} {customer.city}</p>
|
||||||
{customer.vatId && <p className="text-xs text-gray-400 mt-0.5">USt-IdNr.: {customer.vatId}</p>}
|
{/* {customer.vatId && <p className="text-xs text-gray-400 mt-0.5">USt-IdNr.: {customer.vatId}</p>} */}
|
||||||
<div className="flex gap-3 mt-2">
|
<div className="flex gap-3 mt-2">
|
||||||
{customer.email && (
|
{customer.email && (
|
||||||
<span className="flex items-center gap-1 text-xs text-gray-500">
|
<span className="flex items-center gap-1 text-xs text-gray-500">
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
services:
|
||||||
|
mariadb:
|
||||||
|
image: mariadb:11
|
||||||
|
container_name: annas_mariadb
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: rootpassword
|
||||||
|
MYSQL_DATABASE: annas_rechnungen
|
||||||
|
MYSQL_USER: annas_user
|
||||||
|
MYSQL_PASSWORD: annas_password
|
||||||
|
volumes:
|
||||||
|
- mariadb_data:/var/lib/mysql
|
||||||
|
ports:
|
||||||
|
- "3306:3306"
|
||||||
|
networks:
|
||||||
|
- db_network
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||||
|
start_period: 10s
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
phpmyadmin:
|
||||||
|
image: phpmyadmin:latest
|
||||||
|
container_name: annas_phpmyadmin
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
PMA_HOST: mariadb
|
||||||
|
PMA_PORT: 3306
|
||||||
|
PMA_USER: root
|
||||||
|
PMA_PASSWORD: rootpassword
|
||||||
|
UPLOAD_LIMIT: 100M
|
||||||
|
ports:
|
||||||
|
- "8080:80"
|
||||||
|
depends_on:
|
||||||
|
mariadb:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- db_network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mariadb_data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
db_network:
|
||||||
|
driver: bridge
|
||||||
+13
-15
@@ -10,10 +10,8 @@ services:
|
|||||||
MYSQL_PASSWORD: annas_password
|
MYSQL_PASSWORD: annas_password
|
||||||
volumes:
|
volumes:
|
||||||
- mariadb_data:/var/lib/mysql
|
- mariadb_data:/var/lib/mysql
|
||||||
ports:
|
|
||||||
- "3306:3306"
|
|
||||||
networks:
|
networks:
|
||||||
- db_network
|
- app_network
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||||
start_period: 10s
|
start_period: 10s
|
||||||
@@ -21,27 +19,27 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
phpmyadmin:
|
app:
|
||||||
image: phpmyadmin:latest
|
build: .
|
||||||
container_name: annas_phpmyadmin
|
container_name: annas_app
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
|
||||||
PMA_HOST: mariadb
|
|
||||||
PMA_PORT: 3306
|
|
||||||
PMA_USER: root
|
|
||||||
PMA_PASSWORD: rootpassword
|
|
||||||
UPLOAD_LIMIT: 100M
|
|
||||||
ports:
|
ports:
|
||||||
- "8080:80"
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: mysql://annas_user:annas_password@mariadb:3306/annas_rechnungen
|
||||||
|
AUTH_SECRET: ${AUTH_SECRET}
|
||||||
|
NODE_ENV: production
|
||||||
depends_on:
|
depends_on:
|
||||||
mariadb:
|
mariadb:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
networks:
|
networks:
|
||||||
- db_network
|
- app_network
|
||||||
|
command: >
|
||||||
|
sh -c "npx prisma migrate deploy && npm run start"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
mariadb_data:
|
mariadb_data:
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
db_network:
|
app_network:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|||||||
@@ -0,0 +1,198 @@
|
|||||||
|
---
|
||||||
|
# Namespace
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: annas-rechnungsmanager
|
||||||
|
|
||||||
|
---
|
||||||
|
# Secret
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: annas-secrets
|
||||||
|
namespace: annas-rechnungsmanager
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
db-root-password: rootpassword
|
||||||
|
db-password: annas_password
|
||||||
|
auth-secret: your-random-secret-here
|
||||||
|
database-url: mysql://annas_user:annas_password@mariadb-service:3306/annas_rechnungen
|
||||||
|
|
||||||
|
---
|
||||||
|
# MariaDB PersistentVolumeClaim
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: mariadb-pvc
|
||||||
|
namespace: annas-rechnungsmanager
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 5Gi
|
||||||
|
|
||||||
|
---
|
||||||
|
# MariaDB Deployment
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: mariadb
|
||||||
|
namespace: annas-rechnungsmanager
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: mariadb
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: mariadb
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: mariadb
|
||||||
|
image: mariadb:11
|
||||||
|
ports:
|
||||||
|
- containerPort: 3306
|
||||||
|
env:
|
||||||
|
- name: MYSQL_ROOT_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: annas-secrets
|
||||||
|
key: db-root-password
|
||||||
|
- name: MYSQL_DATABASE
|
||||||
|
value: annas_rechnungen
|
||||||
|
- name: MYSQL_USER
|
||||||
|
value: annas_user
|
||||||
|
- name: MYSQL_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: annas-secrets
|
||||||
|
key: db-password
|
||||||
|
volumeMounts:
|
||||||
|
- name: mariadb-storage
|
||||||
|
mountPath: /var/lib/mysql
|
||||||
|
livenessProbe:
|
||||||
|
exec:
|
||||||
|
command: ["healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
readinessProbe:
|
||||||
|
exec:
|
||||||
|
command: ["healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 5
|
||||||
|
volumes:
|
||||||
|
- name: mariadb-storage
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: mariadb-pvc
|
||||||
|
|
||||||
|
---
|
||||||
|
# MariaDB Service
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: mariadb-service
|
||||||
|
namespace: annas-rechnungsmanager
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: mariadb
|
||||||
|
ports:
|
||||||
|
- port: 3306
|
||||||
|
targetPort: 3306
|
||||||
|
|
||||||
|
---
|
||||||
|
# App Deployment
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: annas-app
|
||||||
|
namespace: annas-rechnungsmanager
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: annas-app
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: annas-app
|
||||||
|
spec:
|
||||||
|
initContainers:
|
||||||
|
- name: migrate
|
||||||
|
image: annas-rechnungsmanager:latest
|
||||||
|
command: ["npx", "prisma", "migrate", "deploy"]
|
||||||
|
env:
|
||||||
|
- name: DATABASE_URL
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: annas-secrets
|
||||||
|
key: database-url
|
||||||
|
containers:
|
||||||
|
- name: annas-app
|
||||||
|
image: annas-rechnungsmanager:latest
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
env:
|
||||||
|
- name: DATABASE_URL
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: annas-secrets
|
||||||
|
key: database-url
|
||||||
|
- name: AUTH_SECRET
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: annas-secrets
|
||||||
|
key: auth-secret
|
||||||
|
- name: NODE_ENV
|
||||||
|
value: production
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 3000
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 5
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: 256Mi
|
||||||
|
cpu: 250m
|
||||||
|
limits:
|
||||||
|
memory: 512Mi
|
||||||
|
cpu: 500m
|
||||||
|
|
||||||
|
---
|
||||||
|
# App Service
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: annas-app-service
|
||||||
|
namespace: annas-rechnungsmanager
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: annas-app
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 3000
|
||||||
|
|
||||||
|
---
|
||||||
|
# Ingress
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: annas-app-ingress
|
||||||
|
namespace: annas-rechnungsmanager
|
||||||
|
annotations:
|
||||||
|
nginx.ingress.kubernetes.io/rewrite-target: /
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: rechnungsmanager.local
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: annas-app-service
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "docker-compose up -d && react-router dev",
|
"dev": "docker-compose -f db/docker-compose.yml up -d && react-router dev",
|
||||||
"build": "react-router build",
|
"build": "react-router build",
|
||||||
"start": "react-router-serve ./build/server/index.js",
|
"start": "react-router-serve ./build/server/index.js",
|
||||||
"typecheck": "react-router typegen && tsc",
|
"typecheck": "react-router typegen && tsc",
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `vatId` on the `customers` table. All the data in the column will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `customers` DROP COLUMN `vatId`;
|
||||||
@@ -52,7 +52,6 @@ model Customer {
|
|||||||
companyId String
|
companyId String
|
||||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||||
name String
|
name String
|
||||||
vatId String?
|
|
||||||
taxId String?
|
taxId String?
|
||||||
address String
|
address String
|
||||||
zip String
|
zip String
|
||||||
|
|||||||
Reference in New Issue
Block a user