Skip to content

Commit a09aabb

Browse files
feat: restructured backend layer
1 parent 16e5f13 commit a09aabb

22 files changed

Lines changed: 1820 additions & 43 deletions

File tree

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ VITE_APP_NAME=GhostAPI
1515
PORT=3001
1616
LOG_LEVEL=info
1717
NODE_ENV=development
18+
CORS_ORIGINS=http://localhost:3000,http://localhost:3002
1819

1920
# Auth — replace in non-dev environments. Must be at least 32 chars.
2021
JWT_SECRET=change-me-change-me-change-me-change-me

AGENTS.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# AGENTS.md
2+
3+
This file provides guidance to Codex agents when working in this repository.
4+
5+
## Repository Status
6+
7+
Pre-implementation. The repo currently contains only planning documents (`README.md`, `docs/SYNOPSIS.md`, `docs/SETUP.md`) and the banner asset — no source code, no `package.json`, no `docker-compose.yml`, no scaffolding yet. Treat the docs as the spec; when scaffolding the project, follow them rather than improvising structure.
8+
9+
## Product
10+
11+
GhostAPI turns OpenAPI 3.x schemas into runnable mock REST APIs with editable responses, latency/error/auth simulation, request logs, and an in-app playground. Target user: frontend devs unblocked from waiting on a backend. Phase 1 explicitly excludes GraphQL, tRPC, AI generation, collaboration, billing, analytics, and realtime — see `docs/SYNOPSIS.md` for the full out-of-scope list before adding anything.
12+
13+
## Architecture (planned)
14+
15+
The single most important architectural rule: **everything downstream of parsing depends only on a `NormalizedEndpoint` model, never on raw OpenAPI structures.** This abstraction is what enables future GraphQL/tRPC/gRPC parsers to plug in without rewriting the workspace, runtime, logs, or generators. Frontend code and the runtime server must not import OpenAPI types directly.
16+
17+
Pipeline:
18+
19+
```
20+
OpenAPI Upload → packages/parser → NormalizedEndpoint
21+
→ packages/mock-engine (fake data)
22+
→ packages/runtime (dynamic route mounting, latency/auth/error sim)
23+
→ apps/server (Hono) ←→ apps/app (React SPA workspace)
24+
←→ apps/web (Next.js public site)
25+
```
26+
27+
Planned monorepo layout (Turborepo + pnpm workspaces):
28+
29+
- `apps/web`**Public** Next.js App Router site (port 3000). Landing, docs, blog, marketing, SEO-sensitive pages. **Does NOT contain auth, login/register, dashboard logic, the API workspace, or any protected flow.** Lives here purely for SSR/SEO.
30+
- `apps/app`**Protected** React + Vite SPA (port 3002). Login, register, projects, the unified API workspace, logs, settings — every authenticated flow. Built as an SPA so it can be packaged into Electron later. Stack: React 19, Vite 6, React Router v7 (data router), TanStack Query (server state), Zustand (UI/editor state — do not mix the two), React Hook Form, Tailwind v4 via `@tailwindcss/vite`, consumes `@ghostapi/ui`. Env validated via `loadVitePublicEnv` from `@ghostapi/config` against `import.meta.env` (must be `VITE_*`-prefixed).
31+
- `apps/server` — Hono backend (port 3001). API routes, auth, DB, runtime orchestration, schema ingestion. **No frontend rendering.** Serves both web and app over the same API surface.
32+
- `packages/parser` — OpenAPI validation, extraction, normalization. Future protocol parsers must produce the same `NormalizedEndpoint` shape.
33+
- `packages/runtime` — Dynamic mock runtime: route mounting, latency/auth/error simulation. Kept isolated from data generation.
34+
- `packages/mock-engine` — Schema-aware fake data generation (Faker.js-based). Isolated from runtime logic.
35+
- `packages/types` — Shared DTOs, enums, normalized endpoint types. Avoid duplicating types across apps.
36+
- `packages/ui` — Shared design system. shadcn/ui is initialized here (`components.json` lives in this package). Layout: `components/` for shadcn primitives, `layouts/` for shells, `blocks/` for GhostAPI-specific composed UI (endpoint sidebar, request builder, log viewer, etc.), `lib/utils.ts` for `cn`, `styles/globals.css` for theme tokens. **Theme tokens, colors, spacing, and typography live ONLY here** — apps/web's `globals.css` just `@import`s this file. Add primitives via `cd packages/ui && pnpm dlx shadcn@latest add <name>` — but expect to customize the generated file to match the GhostAPI variants (e.g. Button uses `primary | secondary | tertiary | destructive`, not the shadcn defaults). Storybook lives in `.storybook/` here; stories colocate with components as `<name>.stories.tsx`. Run with `pnpm --filter @ghostapi/ui storybook`.
37+
- `packages/config` — Zod env validation, tsconfig, runtime configs. Startup must fail loudly on invalid env.
38+
39+
## Product Surface — Unified API Workspace
40+
41+
The API Workspace is the heart of the product. It deliberately merges endpoint browsing, request building, response viewing, and mock behavior configuration into one screen. **Do not split these into separate dashboard pages, separate endpoint editors, or a disconnected playground** — that splits the `Request → Response` mental model the product is built around.
42+
43+
## Stack (planned)
44+
45+
Public frontend (`apps/web`): Next.js App Router, TypeScript, Tailwind, shadcn/ui (consumed from `@ghostapi/ui`).
46+
47+
Protected frontend (`apps/app`): React 19, Vite 6, TypeScript, Tailwind v4, shadcn/ui (consumed from `@ghostapi/ui`), React Router v7 data router, TanStack Query (server state), Zustand (UI/editor/builder state — do not mix the two), React Hook Form, Monaco Editor (when the request body editor lands). The SPA exists in addition to the Next site so it can be packaged into Electron later.
48+
49+
Backend: Hono, TypeScript, Zod, Prisma, Pino (structured logs only — no `console.log`).
50+
51+
Infra: PostgreSQL (in Docker from day one — no local installs, no cloud DBs for dev), Redis (added in Phase 1 even though uses are minimal, to avoid migration pain later), Docker Compose, Turborepo, pnpm.
52+
53+
## Planned Commands
54+
55+
Per `README.md` and `docs/SETUP.md`. None of these work yet — they're the contract for scaffolding.
56+
57+
```bash
58+
pnpm install # install workspace deps
59+
docker compose up -d # start postgres + redis
60+
cd apps/server && pnpm prisma migrate dev # run migrations
61+
pnpm dev # turbo dev across apps (web :3000, app :3002, server :3001)
62+
63+
# Single-app dev:
64+
pnpm --filter @ghostapi/app dev # Vite dev server for the protected SPA
65+
pnpm --filter @ghostapi/web dev # Next.js dev server for the public site
66+
pnpm --filter @ghostapi/server dev # Hono backend
67+
```
68+
69+
`turbo.json` defines `build`, `dev` (uncached), `lint`, `test`, `build-storybook`, and `storybook` (uncached, persistent). Pre-commit (Husky + lint-staged) and CI must run lint, typecheck, tests, and build. Storybook is run on demand: `pnpm --filter @ghostapi/ui storybook` for local review, `pnpm --filter @ghostapi/ui build-storybook` to produce a static bundle.
70+
71+
## Conventions
72+
73+
- TypeScript `"strict": true` everywhere. Avoid `any`. Prefer Zod-validated DTOs at boundaries.
74+
- Prisma is **persistence only** — no business logic in models. Columns are `snake_case`. Every table has `created_at` / `updated_at`. **UUIDs only**, never incrementing IDs.
75+
- Env vars validated via Zod in `packages/config/env.ts`. Never read `process.env` directly elsewhere. The package exposes three loaders: `loadServerEnv` (Hono backend), `loadPublicEnv` (Next public site, `NEXT_PUBLIC_*`), `loadVitePublicEnv` (React SPA, `VITE_*`).
76+
- Uploaded OpenAPI schemas must be validated, sanitized, and parsed safely. **Never `eval` uploaded schemas or execute uploaded JavaScript.**
77+
- Design language: dark-first, terminal-inspired, sharp spacing, monospace where it earns its place. Avoid SaaS-style cards, gradients, marketing-dashboard layouts.
78+
- Test priorities: parser engine, mock generation determinism, runtime endpoint serving (Vitest).
79+
80+
## Docs to Read First
81+
82+
- `docs/SYNOPSIS.md` — product scope, phases, database tables, what's explicitly out of scope.
83+
- `docs/SETUP.md` — repo structure, stack rationale, engineering rules, scaffolding steps.
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
-- CreateSchema
2+
CREATE SCHEMA IF NOT EXISTS "public";
3+
4+
-- CreateEnum
5+
CREATE TYPE "project_role" AS ENUM ('OWNER', 'ADMIN', 'EDITOR', 'VIEWER');
6+
7+
-- CreateTable
8+
CREATE TABLE "users" (
9+
"id" UUID NOT NULL,
10+
"email" TEXT NOT NULL,
11+
"password_hash" TEXT NOT NULL,
12+
"name" TEXT,
13+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
14+
"updated_at" TIMESTAMP(3) NOT NULL,
15+
16+
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
17+
);
18+
19+
-- CreateTable
20+
CREATE TABLE "sessions" (
21+
"id" UUID NOT NULL,
22+
"user_id" UUID NOT NULL,
23+
"refresh_token_hash" TEXT NOT NULL,
24+
"user_agent" TEXT,
25+
"ip_address" TEXT,
26+
"expires_at" TIMESTAMP(3) NOT NULL,
27+
"revoked_at" TIMESTAMP(3),
28+
"last_used_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
29+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
30+
"updated_at" TIMESTAMP(3) NOT NULL,
31+
32+
CONSTRAINT "sessions_pkey" PRIMARY KEY ("id")
33+
);
34+
35+
-- CreateTable
36+
CREATE TABLE "password_resets" (
37+
"id" UUID NOT NULL,
38+
"user_id" UUID NOT NULL,
39+
"token_hash" TEXT NOT NULL,
40+
"expires_at" TIMESTAMP(3) NOT NULL,
41+
"used_at" TIMESTAMP(3),
42+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
43+
"updated_at" TIMESTAMP(3) NOT NULL,
44+
45+
CONSTRAINT "password_resets_pkey" PRIMARY KEY ("id")
46+
);
47+
48+
-- CreateTable
49+
CREATE TABLE "projects" (
50+
"id" UUID NOT NULL,
51+
"owner_id" UUID NOT NULL,
52+
"name" TEXT NOT NULL,
53+
"slug" TEXT NOT NULL,
54+
"description" TEXT,
55+
"icon" TEXT,
56+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
57+
"updated_at" TIMESTAMP(3) NOT NULL,
58+
59+
CONSTRAINT "projects_pkey" PRIMARY KEY ("id")
60+
);
61+
62+
-- CreateTable
63+
CREATE TABLE "project_members" (
64+
"id" UUID NOT NULL,
65+
"project_id" UUID NOT NULL,
66+
"user_id" UUID NOT NULL,
67+
"role" "project_role" NOT NULL DEFAULT 'VIEWER',
68+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
69+
"updated_at" TIMESTAMP(3) NOT NULL,
70+
71+
CONSTRAINT "project_members_pkey" PRIMARY KEY ("id")
72+
);
73+
74+
-- CreateTable
75+
CREATE TABLE "environments" (
76+
"id" UUID NOT NULL,
77+
"project_id" UUID NOT NULL,
78+
"name" TEXT NOT NULL,
79+
"base_url" TEXT NOT NULL,
80+
"variables" JSONB NOT NULL DEFAULT '{}',
81+
"headers" JSONB NOT NULL DEFAULT '{}',
82+
"auth_config" JSONB NOT NULL DEFAULT '{}',
83+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
84+
"updated_at" TIMESTAMP(3) NOT NULL,
85+
86+
CONSTRAINT "environments_pkey" PRIMARY KEY ("id")
87+
);
88+
89+
-- CreateTable
90+
CREATE TABLE "schemas" (
91+
"id" UUID NOT NULL,
92+
"project_id" UUID NOT NULL,
93+
"version" INTEGER NOT NULL DEFAULT 1,
94+
"content" JSONB NOT NULL,
95+
"metadata" JSONB NOT NULL DEFAULT '{}',
96+
"uploaded_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
97+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
98+
"updated_at" TIMESTAMP(3) NOT NULL,
99+
100+
CONSTRAINT "schemas_pkey" PRIMARY KEY ("id")
101+
);
102+
103+
-- CreateTable
104+
CREATE TABLE "endpoints" (
105+
"id" UUID NOT NULL,
106+
"project_id" UUID NOT NULL,
107+
"method" TEXT NOT NULL,
108+
"path" TEXT NOT NULL,
109+
"group" TEXT NOT NULL DEFAULT 'default',
110+
"request_schema" JSONB NOT NULL,
111+
"response_schema" JSONB NOT NULL,
112+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
113+
"updated_at" TIMESTAMP(3) NOT NULL,
114+
115+
CONSTRAINT "endpoints_pkey" PRIMARY KEY ("id")
116+
);
117+
118+
-- CreateTable
119+
CREATE TABLE "endpoint_configs" (
120+
"id" UUID NOT NULL,
121+
"endpoint_id" UUID NOT NULL,
122+
"latency_ms" INTEGER NOT NULL DEFAULT 0,
123+
"status_code" INTEGER,
124+
"auth_required" BOOLEAN NOT NULL DEFAULT false,
125+
"error_chance" DOUBLE PRECISION NOT NULL DEFAULT 0,
126+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
127+
"updated_at" TIMESTAMP(3) NOT NULL,
128+
129+
CONSTRAINT "endpoint_configs_pkey" PRIMARY KEY ("id")
130+
);
131+
132+
-- CreateTable
133+
CREATE TABLE "endpoint_responses" (
134+
"id" UUID NOT NULL,
135+
"endpoint_id" UUID NOT NULL,
136+
"status" INTEGER NOT NULL DEFAULT 200,
137+
"body" JSONB NOT NULL,
138+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
139+
"updated_at" TIMESTAMP(3) NOT NULL,
140+
141+
CONSTRAINT "endpoint_responses_pkey" PRIMARY KEY ("id")
142+
);
143+
144+
-- CreateTable
145+
CREATE TABLE "request_logs" (
146+
"id" UUID NOT NULL,
147+
"project_id" UUID NOT NULL,
148+
"endpoint_id" UUID,
149+
"method" TEXT NOT NULL,
150+
"path" TEXT NOT NULL,
151+
"status" INTEGER NOT NULL,
152+
"duration_ms" INTEGER NOT NULL,
153+
"headers" JSONB NOT NULL DEFAULT '{}',
154+
"body" JSONB,
155+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
156+
157+
CONSTRAINT "request_logs_pkey" PRIMARY KEY ("id")
158+
);
159+
160+
-- CreateIndex
161+
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
162+
163+
-- CreateIndex
164+
CREATE UNIQUE INDEX "sessions_refresh_token_hash_key" ON "sessions"("refresh_token_hash");
165+
166+
-- CreateIndex
167+
CREATE INDEX "sessions_user_id_idx" ON "sessions"("user_id");
168+
169+
-- CreateIndex
170+
CREATE INDEX "sessions_expires_at_idx" ON "sessions"("expires_at");
171+
172+
-- CreateIndex
173+
CREATE UNIQUE INDEX "password_resets_token_hash_key" ON "password_resets"("token_hash");
174+
175+
-- CreateIndex
176+
CREATE INDEX "password_resets_user_id_idx" ON "password_resets"("user_id");
177+
178+
-- CreateIndex
179+
CREATE INDEX "password_resets_expires_at_idx" ON "password_resets"("expires_at");
180+
181+
-- CreateIndex
182+
CREATE UNIQUE INDEX "projects_slug_key" ON "projects"("slug");
183+
184+
-- CreateIndex
185+
CREATE INDEX "projects_owner_id_idx" ON "projects"("owner_id");
186+
187+
-- CreateIndex
188+
CREATE INDEX "project_members_user_id_idx" ON "project_members"("user_id");
189+
190+
-- CreateIndex
191+
CREATE UNIQUE INDEX "project_members_project_id_user_id_key" ON "project_members"("project_id", "user_id");
192+
193+
-- CreateIndex
194+
CREATE UNIQUE INDEX "environments_project_id_name_key" ON "environments"("project_id", "name");
195+
196+
-- CreateIndex
197+
CREATE INDEX "schemas_project_id_idx" ON "schemas"("project_id");
198+
199+
-- CreateIndex
200+
CREATE INDEX "endpoints_project_id_group_idx" ON "endpoints"("project_id", "group");
201+
202+
-- CreateIndex
203+
CREATE UNIQUE INDEX "endpoints_project_id_method_path_key" ON "endpoints"("project_id", "method", "path");
204+
205+
-- CreateIndex
206+
CREATE UNIQUE INDEX "endpoint_configs_endpoint_id_key" ON "endpoint_configs"("endpoint_id");
207+
208+
-- CreateIndex
209+
CREATE UNIQUE INDEX "endpoint_responses_endpoint_id_status_key" ON "endpoint_responses"("endpoint_id", "status");
210+
211+
-- CreateIndex
212+
CREATE INDEX "request_logs_project_id_created_at_idx" ON "request_logs"("project_id", "created_at");
213+
214+
-- CreateIndex
215+
CREATE INDEX "request_logs_endpoint_id_created_at_idx" ON "request_logs"("endpoint_id", "created_at");
216+
217+
-- AddForeignKey
218+
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
219+
220+
-- AddForeignKey
221+
ALTER TABLE "password_resets" ADD CONSTRAINT "password_resets_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
222+
223+
-- AddForeignKey
224+
ALTER TABLE "projects" ADD CONSTRAINT "projects_owner_id_fkey" FOREIGN KEY ("owner_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
225+
226+
-- AddForeignKey
227+
ALTER TABLE "project_members" ADD CONSTRAINT "project_members_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
228+
229+
-- AddForeignKey
230+
ALTER TABLE "project_members" ADD CONSTRAINT "project_members_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
231+
232+
-- AddForeignKey
233+
ALTER TABLE "environments" ADD CONSTRAINT "environments_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
234+
235+
-- AddForeignKey
236+
ALTER TABLE "schemas" ADD CONSTRAINT "schemas_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
237+
238+
-- AddForeignKey
239+
ALTER TABLE "endpoints" ADD CONSTRAINT "endpoints_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
240+
241+
-- AddForeignKey
242+
ALTER TABLE "endpoint_configs" ADD CONSTRAINT "endpoint_configs_endpoint_id_fkey" FOREIGN KEY ("endpoint_id") REFERENCES "endpoints"("id") ON DELETE CASCADE ON UPDATE CASCADE;
243+
244+
-- AddForeignKey
245+
ALTER TABLE "endpoint_responses" ADD CONSTRAINT "endpoint_responses_endpoint_id_fkey" FOREIGN KEY ("endpoint_id") REFERENCES "endpoints"("id") ON DELETE CASCADE ON UPDATE CASCADE;
246+
247+
-- AddForeignKey
248+
ALTER TABLE "request_logs" ADD CONSTRAINT "request_logs_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
249+
250+
-- AddForeignKey
251+
ALTER TABLE "request_logs" ADD CONSTRAINT "request_logs_endpoint_id_fkey" FOREIGN KEY ("endpoint_id") REFERENCES "endpoints"("id") ON DELETE SET NULL ON UPDATE CASCADE;

0 commit comments

Comments
 (0)