claude-fix: schema bug, wrong package import, dotenv removal, workspace deps, route mount
This commit is contained in:
parent
d659bf5d44
commit
f61fd26662
@ -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.
|
||||||
|
|||||||
52
apps/api/drizzle/0000_empty_talon.sql
Normal file
52
apps/api/drizzle/0000_empty_talon.sql
Normal 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 $$;
|
||||||
262
apps/api/drizzle/meta/0000_snapshot.json
Normal file
262
apps/api/drizzle/meta/0000_snapshot.json
Normal 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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
apps/api/drizzle/meta/_journal.json
Normal file
13
apps/api/drizzle/meta/_journal.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1779503479059,
|
||||||
|
"tag": "0000_empty_talon",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -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",
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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
3350
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user