claude-fix: schema bug, wrong package import, dotenv removal, workspace deps, route mount

This commit is contained in:
Dennis (via Claude+Gemma) 2026-05-23 04:36:10 +02:00
parent d659bf5d44
commit f61fd26662
12 changed files with 3829 additions and 35 deletions

View File

@ -1,3 +1,130 @@
# EmberClone — Generation Log # EmberClone — Generation Log
Schritt-für-Schritt-Historie aller Gemma-Code-Generierungen. Schritt-für-Schritt-Historie aller Gemma-Code-Generierungen.
## EmberClone Codegen-Run gestartet (2026-05-23 04:24:46)
- `04:24:46` **INFO** Specs: 18 Files zu generieren
- `04:24:46` **INFO** vLLM: http://127.0.0.1:8000/v1/chat/completions, Model: gemma-4-31b
- `04:24:46` **INFO** Pinging Gemma …
- `04:24:46` **INFO** Gemma pong ok: 'pong'
## Generiere packages/shared/src/schemas.ts (2026-05-23 04:24:46)
- `04:24:46` **INFO** Attempt 1/3 für packages/shared/src/schemas.ts
- `04:25:03` **INFO** wrote 1956 chars in 16.8s
- `04:25:03` **INFO** syntax check ok
## Generiere packages/shared/src/index.ts (2026-05-23 04:25:03)
- `04:25:03` **INFO** Attempt 1/3 für packages/shared/src/index.ts
- `04:25:03` **INFO** wrote 25 chars in 0.3s
- `04:25:03` **WARN** syntax check failed: too short (25 chars)
- `04:25:03` **INFO** Attempt 2/3 für packages/shared/src/index.ts
- `04:25:03` **INFO** wrote 25 chars in 0.2s
- `04:25:03` **WARN** syntax check failed: too short (25 chars)
- `04:25:03` **INFO** Attempt 3/3 für packages/shared/src/index.ts
- `04:25:04` **INFO** wrote 25 chars in 0.2s
- `04:25:04` **WARN** syntax check failed: too short (25 chars)
- `04:25:04` **ERROR** GAVE UP after 3 attempts: too short (25 chars)
## Generiere apps/api/src/db/schema.ts (2026-05-23 04:25:04)
- `04:25:04` **INFO** Attempt 1/3 für apps/api/src/db/schema.ts
- `04:25:16` **INFO** wrote 1440 chars in 12.2s
- `04:25:16` **INFO** syntax check ok
## Generiere apps/api/src/db/index.ts (2026-05-23 04:25:16)
- `04:25:16` **INFO** Attempt 1/3 für apps/api/src/db/index.ts
- `04:25:19` **INFO** wrote 328 chars in 2.8s
- `04:25:19` **INFO** syntax check ok
## Generiere apps/api/src/db/migrate.ts (2026-05-23 04:25:19)
- `04:25:19` **INFO** Attempt 1/3 für apps/api/src/db/migrate.ts
- `04:25:28` **INFO** wrote 1105 chars in 9.4s
- `04:25:28` **INFO** syntax check ok
## Generiere apps/api/src/routes/auth.ts (2026-05-23 04:25:28)
- `04:25:28` **INFO** Attempt 1/3 für apps/api/src/routes/auth.ts
- `04:25:45` **INFO** wrote 1852 chars in 17.2s
- `04:25:45` **INFO** syntax check ok
## Generiere apps/api/src/routes/time-entries.ts (2026-05-23 04:25:45)
- `04:25:45` **INFO** Attempt 1/3 für apps/api/src/routes/time-entries.ts
- `04:26:21` **INFO** wrote 3875 chars in 35.8s
- `04:26:21` **INFO** syntax check ok
## Generiere apps/api/src/routes/index.ts (2026-05-23 04:26:21)
- `04:26:21` **INFO** Attempt 1/3 für apps/api/src/routes/index.ts
- `04:26:24` **INFO** wrote 318 chars in 3.0s
- `04:26:24` **INFO** syntax check ok
## Generiere apps/api/src/index.ts (2026-05-23 04:26:24)
- `04:26:24` **INFO** Attempt 1/3 für apps/api/src/index.ts
- `04:26:32` **INFO** wrote 806 chars in 8.0s
- `04:26:32` **INFO** syntax check ok
## Generiere apps/web/src/main.tsx (2026-05-23 04:26:32)
- `04:26:32` **INFO** Attempt 1/3 für apps/web/src/main.tsx
- `04:26:39` **INFO** wrote 855 chars in 7.1s
- `04:26:39` **INFO** syntax check ok
## Generiere apps/web/src/lib/api.ts (2026-05-23 04:26:39)
- `04:26:39` **INFO** Attempt 1/3 für apps/web/src/lib/api.ts
- `04:26:54` **INFO** wrote 1625 chars in 14.2s
- `04:26:54` **INFO** syntax check ok
## Generiere apps/web/src/pages/Login.tsx (2026-05-23 04:26:54)
- `04:26:54` **INFO** Attempt 1/3 für apps/web/src/pages/Login.tsx
- `04:27:17` **INFO** wrote 2773 chars in 23.3s
- `04:27:17` **INFO** syntax check ok
## Generiere apps/web/src/pages/Dashboard.tsx (2026-05-23 04:27:17)
- `04:27:17` **INFO** Attempt 1/3 für apps/web/src/pages/Dashboard.tsx
- `04:27:37` **INFO** wrote 2229 chars in 20.1s
- `04:27:37` **INFO** syntax check ok
## Generiere apps/web/src/pages/TimeEntries.tsx (2026-05-23 04:27:37)
- `04:27:37` **INFO** Attempt 1/3 für apps/web/src/pages/TimeEntries.tsx
- `04:28:26` **INFO** wrote 6015 chars in 48.7s
- `04:28:26` **INFO** syntax check ok
## Generiere apps/web/src/App.tsx (2026-05-23 04:28:26)
- `04:28:26` **INFO** Attempt 1/3 für apps/web/src/App.tsx
- `04:28:39` **INFO** wrote 1466 chars in 13.6s
- `04:28:39` **INFO** syntax check ok
## Generiere apps/web/src/index.css (2026-05-23 04:28:39)
- `04:28:39` **INFO** Attempt 1/3 für apps/web/src/index.css
- `04:28:41` **INFO** wrote 149 chars in 1.6s
- `04:28:41` **INFO** syntax check ok
## Generiere apps/web/postcss.config.cjs (2026-05-23 04:28:41)
- `04:28:41` **INFO** Attempt 1/3 für apps/web/postcss.config.cjs
- `04:28:42` **INFO** wrote 81 chars in 0.8s
- `04:28:42` **INFO** syntax check ok
## Generiere apps/web/tailwind.config.ts (2026-05-23 04:28:42)
- `04:28:42` **INFO** Attempt 1/3 für apps/web/tailwind.config.ts
- `04:28:45` **INFO** wrote 294 chars in 3.2s
- `04:28:45` **INFO** syntax check ok
## Codegen-Run beendet (2026-05-23 04:28:45)
- `04:28:45` **INFO** ok: 17/18, fail: 1/18
- `04:28:45` **WARN** 1 Files mit final-Fehler. Manuelle Inspektion nötig.

View File

@ -0,0 +1,52 @@
CREATE TABLE IF NOT EXISTS "customers" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" text NOT NULL,
"active" boolean DEFAULT true NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "projects" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" text NOT NULL,
"customer_id" uuid NOT NULL,
"active" boolean DEFAULT true NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "time_entries" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" uuid NOT NULL,
"project_id" uuid,
"description" text NOT NULL,
"start_time" timestamp NOT NULL,
"end_time" timestamp,
"created_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "users" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"email" text NOT NULL,
"name" text NOT NULL,
"role" text NOT NULL,
"password_hash" text NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "users_email_unique" UNIQUE("email")
);
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "projects" ADD CONSTRAINT "projects_customer_id_customers_id_fk" FOREIGN KEY ("customer_id") REFERENCES "public"."customers"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "time_entries" ADD CONSTRAINT "time_entries_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "time_entries" ADD CONSTRAINT "time_entries_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

View File

@ -0,0 +1,262 @@
{
"id": "cf31898f-9a96-4dfd-ac9e-23326daf55fb",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.customers": {
"name": "customers",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"active": {
"name": "active",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.projects": {
"name": "projects",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"customer_id": {
"name": "customer_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"active": {
"name": "active",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"projects_customer_id_customers_id_fk": {
"name": "projects_customer_id_customers_id_fk",
"tableFrom": "projects",
"tableTo": "customers",
"columnsFrom": [
"customer_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.time_entries": {
"name": "time_entries",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"project_id": {
"name": "project_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": true
},
"start_time": {
"name": "start_time",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"end_time": {
"name": "end_time",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"time_entries_user_id_users_id_fk": {
"name": "time_entries_user_id_users_id_fk",
"tableFrom": "time_entries",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"time_entries_project_id_projects_id_fk": {
"name": "time_entries_project_id_projects_id_fk",
"tableFrom": "time_entries",
"tableTo": "projects",
"columnsFrom": [
"project_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"role": {
"name": "role",
"type": "text",
"primaryKey": false,
"notNull": true
},
"password_hash": {
"name": "password_hash",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"users_email_unique": {
"name": "users_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1779503479059,
"tag": "0000_empty_talon",
"breakpoints": true
}
]
}

View File

@ -12,6 +12,7 @@
"db:studio": "drizzle-kit studio" "db:studio": "drizzle-kit studio"
}, },
"dependencies": { "dependencies": {
"@emberclone/shared": "workspace:*",
"@fastify/cookie": "^9.3.1", "@fastify/cookie": "^9.3.1",
"@fastify/cors": "^9.0.1", "@fastify/cors": "^9.0.1",
"@fastify/jwt": "^8.0.1", "@fastify/jwt": "^8.0.1",

View File

@ -1,4 +1,3 @@
import "dotenv/config"
import Fastify from "fastify" import Fastify from "fastify"
import cors from "@fastify/cors" import cors from "@fastify/cors"
import cookie from "@fastify/cookie" import cookie from "@fastify/cookie"

View File

@ -9,6 +9,7 @@
"preview": "vite preview --port 5174" "preview": "vite preview --port 5174"
}, },
"dependencies": { "dependencies": {
"@emberclone/shared": "workspace:*",
"@tanstack/react-query": "^5.59.0", "@tanstack/react-query": "^5.59.0",
"@tanstack/react-router": "^1.62.7", "@tanstack/react-router": "^1.62.7",
"react": "^18.3.1", "react": "^18.3.1",

View File

@ -1,4 +1,4 @@
import { z } from "@rmpks/shared" import type { TimeEntryInsert } from "@emberclone/shared"
const API_BASE = "/api" const API_BASE = "/api"
@ -20,7 +20,9 @@ async function request<T>(endpoint: string, options: RequestInit = {}): Promise<
if (!response.ok) { if (!response.ok) {
const errorData = await response.json().catch(() => ({})) const errorData = await response.json().catch(() => ({}))
throw new Error(errorData.message || `API Error: ${response.status}`) const err: any = new Error(errorData.message || `API Error: ${response.status}`)
err.status = response.status
throw err
} }
return response.json() return response.json()
@ -32,25 +34,27 @@ export const api = {
method: "POST", method: "POST",
body: JSON.stringify({ email, password }) body: JSON.stringify({ email, password })
}) })
localStorage.setItem("auth_token", data.token) if (data?.token) {
localStorage.setItem("auth_token", data.token)
}
return data return data
}, },
logout() { logout() {
localStorage.removeItem("auth_token") localStorage.removeItem("auth_token")
return request("/auth/logout", { method: "POST" }).catch(() => {})
}, },
async getMe() { async getMe() {
return request("/auth/me") return request<{ id: string; email: string; name: string; role: "admin" | "user" }>("/auth/me")
}, },
async listTimeEntries(opts?: Record<string, string>) { async listTimeEntries(opts?: Record<string, string>) {
const query = new URLSearchParams(opts).toString() const query = opts ? `?${new URLSearchParams(opts).toString()}` : ""
const endpoint = query ? `/time-entries?${query}` : "/time-entries" return request<any[]>(`/time-entries${query}`)
return request(`/time-entries${query ? `?${query}` : ""}`)
}, },
async createTimeEntry(data: z.infer<typeof z.TimeEntrySchema>) { async createTimeEntry(data: Partial<TimeEntryInsert>) {
return request("/time-entries", { return request("/time-entries", {
method: "POST", method: "POST",
body: JSON.stringify(data) body: JSON.stringify(data)
@ -62,4 +66,4 @@ export const api = {
method: "DELETE" method: "DELETE"
}) })
} }
} }

View File

@ -1,8 +1,7 @@
import React from "react" import React from "react"
import ReactDOM from "react-dom/client" import ReactDOM from "react-dom/client"
import { RouterProvider, createRouter } from "@tanstack/react-router"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query" import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { routeTree } from "./routeTree.gen" import App from "./App"
import "./index.css" import "./index.css"
const queryClient = new QueryClient({ const queryClient = new QueryClient({
@ -14,24 +13,10 @@ const queryClient = new QueryClient({
}, },
}) })
const router = createRouter({
routeTree,
context: {
queryClient,
},
})
// Register the router for type safety
declare module "@tanstack/react-router" {
interface Register {
router: typeof router
}
}
ReactDOM.createRoot(document.getElementById("root")!).render( ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode> <React.StrictMode>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<RouterProvider router={router} /> <App />
</QueryClientProvider> </QueryClientProvider>
</React.StrictMode> </React.StrictMode>
) )

View File

@ -1,7 +1,7 @@
import { useState } from "react" import { useState } from "react"
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query" import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import { api } from "../lib/api" import { api } from "../lib/api"
import { z } from "@rmpks/shared" import type { TimeEntryInsert } from "@emberclone/shared"
export default function TimeEntries() { export default function TimeEntries() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
@ -18,7 +18,7 @@ export default function TimeEntries() {
}) })
const createMutation = useMutation({ const createMutation = useMutation({
mutationFn: (data: z.infer<typeof z.TimeEntrySchema>) => api.createTimeEntry(data), mutationFn: (data: Partial<TimeEntryInsert>) => api.createTimeEntry(data),
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["time-entries"] }) queryClient.invalidateQueries({ queryKey: ["time-entries"] })
setFormData({ description: "", startTime: "", endTime: "", projectId: "" }) setFormData({ description: "", startTime: "", endTime: "", projectId: "" })
@ -36,10 +36,10 @@ export default function TimeEntries() {
e.preventDefault() e.preventDefault()
createMutation.mutate({ createMutation.mutate({
description: formData.description, description: formData.description,
startTime: new Date(formData.startTime).toISOString(), startTime: new Date(formData.startTime) as any,
endTime: new Date(formData.endTime).toISOString(), endTime: new Date(formData.endTime) as any,
projectId: formData.projectId projectId: formData.projectId || undefined
} as z.infer<typeof z.TimeEntrySchema>) })
} }
if (isLoading) return <div className="p-6 text-gray-500">Loading entries...</div> if (isLoading) return <div className="p-6 text-gray-500">Loading entries...</div>

View File

@ -2,7 +2,7 @@ import { z } from "zod"
export const UserRoleSchema = z.enum(["admin", "user"]) export const UserRoleSchema = z.enum(["admin", "user"])
export const UserInsertSchema = UserRoleSchema.extend({ export const UserInsertSchema = z.object({
email: z.string().email(), email: z.string().email(),
name: z.string().min(1), name: z.string().min(1),
role: UserRoleSchema, role: UserRoleSchema,

3350
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff