openship 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/.env.example +22 -0
  2. package/.nvmrc +1 -0
  3. package/.prettierrc +7 -0
  4. package/CONTRIBUTING.md +40 -0
  5. package/README.md +164 -0
  6. package/apps/api/.env.example +30 -0
  7. package/apps/api/Dockerfile +24 -0
  8. package/apps/api/package.json +37 -0
  9. package/apps/api/src/app.ts +26 -0
  10. package/apps/api/src/config/env.ts +39 -0
  11. package/apps/api/src/config/index.ts +1 -0
  12. package/apps/api/src/index.ts +8 -0
  13. package/apps/api/src/middleware/auth.ts +24 -0
  14. package/apps/api/src/middleware/error-handler.ts +21 -0
  15. package/apps/api/src/middleware/index.ts +3 -0
  16. package/apps/api/src/middleware/rate-limiter.ts +26 -0
  17. package/apps/api/src/modules/auth/auth.controller.ts +26 -0
  18. package/apps/api/src/modules/auth/auth.routes.ts +10 -0
  19. package/apps/api/src/modules/auth/auth.schema.ts +12 -0
  20. package/apps/api/src/modules/auth/auth.service.ts +23 -0
  21. package/apps/api/src/modules/billing/billing.controller.ts +60 -0
  22. package/apps/api/src/modules/billing/billing.routes.ts +26 -0
  23. package/apps/api/src/modules/billing/billing.schema.ts +9 -0
  24. package/apps/api/src/modules/billing/billing.service.ts +33 -0
  25. package/apps/api/src/modules/deployments/deployment.controller.ts +35 -0
  26. package/apps/api/src/modules/deployments/deployment.routes.ts +11 -0
  27. package/apps/api/src/modules/deployments/deployment.schema.ts +7 -0
  28. package/apps/api/src/modules/deployments/deployment.service.ts +23 -0
  29. package/apps/api/src/modules/domains/domain.controller.ts +21 -0
  30. package/apps/api/src/modules/domains/domain.routes.ts +9 -0
  31. package/apps/api/src/modules/domains/domain.service.ts +19 -0
  32. package/apps/api/src/modules/health/health.routes.ts +10 -0
  33. package/apps/api/src/modules/projects/project.controller.ts +29 -0
  34. package/apps/api/src/modules/projects/project.routes.ts +10 -0
  35. package/apps/api/src/modules/projects/project.schema.ts +9 -0
  36. package/apps/api/src/modules/projects/project.service.ts +23 -0
  37. package/apps/api/src/modules/webhooks/webhook.controller.ts +22 -0
  38. package/apps/api/src/modules/webhooks/webhook.routes.ts +12 -0
  39. package/apps/api/src/modules/webhooks/webhook.service.ts +11 -0
  40. package/apps/api/tsconfig.json +12 -0
  41. package/apps/cli/package.json +28 -0
  42. package/apps/cli/src/commands/deploy.ts +11 -0
  43. package/apps/cli/src/commands/init.ts +8 -0
  44. package/apps/cli/src/commands/login.ts +8 -0
  45. package/apps/cli/src/commands/logs.ts +9 -0
  46. package/apps/cli/src/index.ts +21 -0
  47. package/apps/cli/src/lib/api-client.ts +35 -0
  48. package/apps/cli/tsconfig.json +12 -0
  49. package/apps/dashboard/Dockerfile +24 -0
  50. package/apps/dashboard/next.config.mjs +6 -0
  51. package/apps/dashboard/package.json +28 -0
  52. package/apps/dashboard/src/app/(auth)/layout.tsx +6 -0
  53. package/apps/dashboard/src/app/(auth)/login/page.tsx +11 -0
  54. package/apps/dashboard/src/app/(auth)/register/page.tsx +10 -0
  55. package/apps/dashboard/src/app/(dashboard)/billing/page.tsx +10 -0
  56. package/apps/dashboard/src/app/(dashboard)/deployments/page.tsx +8 -0
  57. package/apps/dashboard/src/app/(dashboard)/domains/page.tsx +8 -0
  58. package/apps/dashboard/src/app/(dashboard)/layout.tsx +37 -0
  59. package/apps/dashboard/src/app/(dashboard)/monitoring/page.tsx +8 -0
  60. package/apps/dashboard/src/app/(dashboard)/projects/page.tsx +8 -0
  61. package/apps/dashboard/src/app/(dashboard)/settings/page.tsx +8 -0
  62. package/apps/dashboard/src/app/globals.css +3 -0
  63. package/apps/dashboard/src/app/layout.tsx +15 -0
  64. package/apps/dashboard/src/app/page.tsx +6 -0
  65. package/apps/dashboard/tsconfig.json +11 -0
  66. package/apps/web/Dockerfile +24 -0
  67. package/apps/web/next.config.mjs +6 -0
  68. package/apps/web/package.json +26 -0
  69. package/apps/web/src/app/(marketing)/docs/page.tsx +10 -0
  70. package/apps/web/src/app/(marketing)/layout.tsx +24 -0
  71. package/apps/web/src/app/(marketing)/page.tsx +2 -0
  72. package/apps/web/src/app/(marketing)/pricing/page.tsx +10 -0
  73. package/apps/web/src/app/globals.css +3 -0
  74. package/apps/web/src/app/layout.tsx +16 -0
  75. package/apps/web/src/app/page.tsx +10 -0
  76. package/apps/web/tsconfig.json +11 -0
  77. package/docker-compose.yml +87 -0
  78. package/package.json +31 -0
  79. package/packages/adapters/package.json +27 -0
  80. package/packages/adapters/src/base-adapter.ts +49 -0
  81. package/packages/adapters/src/docker-adapter.ts +43 -0
  82. package/packages/adapters/src/index.ts +4 -0
  83. package/packages/adapters/src/oblien-adapter.ts +46 -0
  84. package/packages/adapters/src/registry.ts +22 -0
  85. package/packages/adapters/tsconfig.json +9 -0
  86. package/packages/core/package.json +27 -0
  87. package/packages/core/src/constants.ts +28 -0
  88. package/packages/core/src/errors.ts +49 -0
  89. package/packages/core/src/index.ts +4 -0
  90. package/packages/core/src/types.ts +47 -0
  91. package/packages/core/src/utils.ts +35 -0
  92. package/packages/core/tsconfig.json +9 -0
  93. package/packages/db/package.json +32 -0
  94. package/packages/db/prisma/schema.prisma +160 -0
  95. package/packages/db/src/index.ts +11 -0
  96. package/packages/db/tsconfig.json +9 -0
  97. package/packages/ui/package.json +38 -0
  98. package/packages/ui/src/components/badge.tsx +29 -0
  99. package/packages/ui/src/components/button.tsx +44 -0
  100. package/packages/ui/src/components/card.tsx +26 -0
  101. package/packages/ui/src/components/status-dot.tsx +25 -0
  102. package/packages/ui/src/globals.css +18 -0
  103. package/packages/ui/src/index.tsx +8 -0
  104. package/packages/ui/src/lib/cn.ts +7 -0
  105. package/packages/ui/tsconfig.json +9 -0
  106. package/pnpm-workspace.yaml +4 -0
  107. package/tooling/tsconfig/base.json +19 -0
  108. package/tooling/tsconfig/nextjs.json +10 -0
  109. package/tooling/tsconfig/node.json +10 -0
  110. package/tooling/tsconfig/package.json +7 -0
  111. package/turbo.json +32 -0
package/.env.example ADDED
@@ -0,0 +1,22 @@
1
+ # See docker-compose.yml for full reference.
2
+ # Copy this file to .env and customize.
3
+
4
+ NODE_ENV=development
5
+
6
+ # ─── Database ────────────────────────────────
7
+ DATABASE_URL=postgresql://openship:openship@localhost:5432/openship
8
+
9
+ # ─── Redis ───────────────────────────────────
10
+ REDIS_URL=redis://localhost:6379
11
+
12
+ # ─── Auth ────────────────────────────────────
13
+ JWT_SECRET=change-me-in-production
14
+
15
+ # ─── Mode ────────────────────────────────────
16
+ # false = self-hosted (no billing)
17
+ # true = cloud (billing, metering, multi-tenant)
18
+ CLOUD_MODE=false
19
+
20
+ # ─── Stripe (required only when CLOUD_MODE=true) ──
21
+ # STRIPE_SECRET_KEY=sk_test_...
22
+ # STRIPE_WEBHOOK_SECRET=whsec_...
package/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 20
package/.prettierrc ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "semi": true,
3
+ "singleQuote": false,
4
+ "tabWidth": 2,
5
+ "trailingComma": "all",
6
+ "printWidth": 100
7
+ }
@@ -0,0 +1,40 @@
1
+ # Contributing to Openship
2
+
3
+ Thank you for your interest in contributing! This guide will help you get started.
4
+
5
+ ## Development Setup
6
+
7
+ 1. **Fork & clone** the repository
8
+ 2. **Install dependencies**: `pnpm install`
9
+ 3. **Set up env**: `cp .env.example .env`
10
+ 4. **Generate DB client**: `pnpm db:generate`
11
+ 5. **Start dev**: `pnpm dev`
12
+
13
+ ## Project Structure
14
+
15
+ - `apps/*` — Deployable applications (web, dashboard, api, cli)
16
+ - `packages/*` — Shared libraries consumed by apps
17
+ - `tooling/*` — Build and lint configurations
18
+
19
+ ## Conventions
20
+
21
+ - **Commits**: Follow [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `docs:`, etc.)
22
+ - **Branches**: `feat/`, `fix/`, `docs/`, `chore/`
23
+ - **Code style**: Prettier is configured — run `pnpm format` before committing
24
+ - **Types**: TypeScript strict mode is enabled everywhere
25
+
26
+ ## API Module Pattern
27
+
28
+ Each API module follows this structure:
29
+
30
+ ```
31
+ modules/<name>/
32
+ ├── <name>.routes.ts # Route definitions
33
+ ├── <name>.controller.ts # Request handlers
34
+ ├── <name>.service.ts # Business logic
35
+ └── <name>.schema.ts # Zod validation schemas
36
+ ```
37
+
38
+ ## Need Help?
39
+
40
+ Open an issue or start a discussion — we're happy to help!
package/README.md ADDED
@@ -0,0 +1,164 @@
1
+ <div align="center">
2
+ <h1>🚀 Openship</h1>
3
+ <p><strong>The open-source, self-hostable deployment platform.</strong></p>
4
+ <p>Deploy anywhere — from your own server to our managed cloud.</p>
5
+
6
+ <br />
7
+
8
+ <a href="#quick-start">Quick Start</a> ·
9
+ <a href="#architecture">Architecture</a> ·
10
+ <a href="#self-hosting">Self-Hosting</a> ·
11
+ <a href="#cloud">Cloud</a> ·
12
+ <a href="#contributing">Contributing</a>
13
+ </div>
14
+
15
+ ---
16
+
17
+ ## Quick Start
18
+
19
+ ### Prerequisites
20
+
21
+ - [Node.js](https://nodejs.org/) >= 20
22
+ - [pnpm](https://pnpm.io/) >= 9
23
+ - [Docker](https://www.docker.com/) (for self-hosting or local development)
24
+
25
+ ### Development
26
+
27
+ ```bash
28
+ # Clone the repo
29
+ git clone https://github.com/openship/openship.git
30
+ cd openship
31
+
32
+ # Install dependencies
33
+ pnpm install
34
+
35
+ # Set up environment
36
+ cp .env.example .env
37
+
38
+ # Generate Prisma client
39
+ pnpm db:generate
40
+
41
+ # Push schema to local SQLite
42
+ pnpm db:push
43
+
44
+ # Start all apps in dev mode
45
+ pnpm dev
46
+ ```
47
+
48
+ This will start:
49
+
50
+ | App | URL |
51
+ | ----------- | ----------------------- |
52
+ | Web | http://localhost:3000 |
53
+ | Dashboard | http://localhost:3001 |
54
+ | API | http://localhost:4000 |
55
+
56
+ ### Self-Hosting with Docker
57
+
58
+ ```bash
59
+ cp .env.example .env
60
+ docker compose up -d
61
+ ```
62
+
63
+ That's it. Openship will be running at `http://localhost:3001`.
64
+
65
+ ---
66
+
67
+ ## Architecture
68
+
69
+ ```
70
+ openship/
71
+ ├── apps/
72
+ │ ├── web/ # Next.js — Marketing site, landing page, docs, pricing
73
+ │ ├── dashboard/ # Next.js — Authenticated deployment dashboard (Vercel-style)
74
+ │ ├── api/ # Hono — Core API engine (auth, deployments, billing, webhooks)
75
+ │ └── cli/ # TypeScript CLI — `openship deploy` from your terminal
76
+
77
+ ├── packages/
78
+ │ ├── adapters/ # Deployment adapters (DockerAdapter, OblienAdapter)
79
+ │ ├── db/ # Prisma schema + client (SQLite local, Postgres cloud)
80
+ │ ├── core/ # Shared types, constants, utilities, error classes
81
+ │ └── ui/ # Shared React component library (Tailwind + shadcn style)
82
+
83
+ ├── tooling/
84
+ │ └── tsconfig/ # Shared TypeScript configurations
85
+
86
+ ├── docker-compose.yml
87
+ ├── turbo.json
88
+ └── pnpm-workspace.yaml
89
+ ```
90
+
91
+ ### API Modules
92
+
93
+ The API is organized into clean, independent modules:
94
+
95
+ | Module | Path | Purpose |
96
+ | -------------- | ----------------------------- | ---------------------------------------- |
97
+ | **Auth** | `modules/auth/` | Registration, login, JWT, sessions |
98
+ | **Projects** | `modules/projects/` | CRUD for deployment projects |
99
+ | **Deployments**| `modules/deployments/` | Build, deploy, rollback, logs |
100
+ | **Domains** | `modules/domains/` | Custom domains, DNS verification, SSL |
101
+ | **Billing** | `modules/billing/` | Plans, subscriptions, usage (cloud only) |
102
+ | **Webhooks** | `modules/webhooks/` | GitHub/GitLab/Bitbucket push triggers |
103
+ | **Health** | `modules/health/` | Health check endpoint for load balancers |
104
+
105
+ ### Adapter Pattern
106
+
107
+ The **adapter pattern** is what makes Openship platform-agnostic:
108
+
109
+ - **`DockerAdapter`** — Self-hosted. Builds and runs containers on the host machine.
110
+ - **`OblienAdapter`** — Cloud. Communicates with the Oblien infrastructure API.
111
+
112
+ The `CLOUD_MODE` environment variable controls which adapter is active. For self-hosted installs, billing is completely disabled.
113
+
114
+ ---
115
+
116
+ ## Self-Hosting
117
+
118
+ Openship is designed to be self-hosted with a single `docker compose up`. The Docker adapter handles all builds and deployments locally on your machine.
119
+
120
+ **Requirements:** Docker Engine, 2GB RAM minimum.
121
+
122
+ ---
123
+
124
+ ## Cloud
125
+
126
+ The hosted version at [openship.cloud](https://openship.cloud) adds:
127
+
128
+ - Managed infrastructure via the Oblien adapter
129
+ - Subscription billing (Stripe)
130
+ - Usage metering (build minutes, bandwidth)
131
+ - Team management and SSO
132
+
133
+ ---
134
+
135
+ ## Tech Stack
136
+
137
+ | Layer | Technology |
138
+ | ---------- | --------------------------------- |
139
+ | Frontend | Next.js 14, React 18, Tailwind |
140
+ | API | Hono (edge-compatible) |
141
+ | Database | Prisma (SQLite / PostgreSQL) |
142
+ | Queue | BullMQ + Redis |
143
+ | CLI | Commander.js, TypeScript |
144
+ | Monorepo | Turborepo + pnpm workspaces |
145
+ | Containers | Docker |
146
+ | Billing | Stripe |
147
+
148
+ ---
149
+
150
+ ## Contributing
151
+
152
+ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
153
+
154
+ 1. Fork the repository
155
+ 2. Create your feature branch (`git checkout -b feat/amazing-feature`)
156
+ 3. Commit your changes (`git commit -m 'feat: add amazing feature'`)
157
+ 4. Push to the branch (`git push origin feat/amazing-feature`)
158
+ 5. Open a Pull Request
159
+
160
+ ---
161
+
162
+ ## License
163
+
164
+ [MIT](LICENSE) — Free to use, self-host, and modify. Cloud/SaaS usage requires the AGPL license terms.
@@ -0,0 +1,30 @@
1
+ # Openship API — Environment Variables
2
+ # Copy this file to .env and fill in the values.
3
+
4
+ NODE_ENV=development
5
+ PORT=4000
6
+
7
+ # ---------- Mode ----------
8
+ # Set to "true" to enable cloud billing, metering, multi-tenant features.
9
+ # Default: false (self-hosted single-tenant mode).
10
+ CLOUD_MODE=false
11
+
12
+ # ---------- Database ----------
13
+ DATABASE_URL=file:./dev.db
14
+
15
+ # ---------- Auth ----------
16
+ JWT_SECRET=change-me-in-production
17
+ JWT_EXPIRES_IN=15m
18
+ JWT_REFRESH_EXPIRES_IN=7d
19
+
20
+ # ---------- Redis ----------
21
+ REDIS_URL=redis://localhost:6379
22
+
23
+ # ---------- Stripe (Cloud mode only) ----------
24
+ # STRIPE_SECRET_KEY=sk_test_...
25
+ # STRIPE_WEBHOOK_SECRET=whsec_...
26
+
27
+ # ---------- GitHub App (for Git push webhooks) ----------
28
+ # GITHUB_APP_ID=
29
+ # GITHUB_PRIVATE_KEY=
30
+ # GITHUB_WEBHOOK_SECRET=
@@ -0,0 +1,24 @@
1
+ # ─── Build stage ────────────────────────────────────────
2
+ FROM node:20-alpine AS builder
3
+ RUN corepack enable && corepack prepare pnpm@9.0.0 --activate
4
+ WORKDIR /app
5
+
6
+ COPY pnpm-workspace.yaml pnpm-lock.yaml* package.json ./
7
+ COPY tooling/ ./tooling/
8
+ COPY packages/ ./packages/
9
+ COPY apps/api/ ./apps/api/
10
+
11
+ RUN pnpm install --frozen-lockfile
12
+ RUN pnpm run build --filter=openship-api...
13
+
14
+ # ─── Production stage ──────────────────────────────────
15
+ FROM node:20-alpine AS runner
16
+ WORKDIR /app
17
+
18
+ COPY --from=builder /app/apps/api/dist ./dist
19
+ COPY --from=builder /app/apps/api/package.json ./
20
+ COPY --from=builder /app/node_modules ./node_modules
21
+
22
+ ENV NODE_ENV=production
23
+ EXPOSE 4000
24
+ CMD ["node", "dist/index.js"]
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "openship-api",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "tsx watch src/index.ts",
8
+ "build": "tsup src/index.ts --format esm --dts",
9
+ "start": "node dist/index.js",
10
+ "lint": "tsc --noEmit",
11
+ "test": "vitest run",
12
+ "clean": "rm -rf dist .turbo node_modules"
13
+ },
14
+ "dependencies": {
15
+ "openship-core": "workspace:*",
16
+ "openship-db": "workspace:*",
17
+ "openship-adapters": "workspace:*",
18
+ "@hono/node-server": "^1.11.0",
19
+ "hono": "^4.4.0",
20
+ "zod": "^3.23.0",
21
+ "jsonwebtoken": "^9.0.0",
22
+ "bcryptjs": "^2.4.3",
23
+ "stripe": "^15.0.0",
24
+ "bullmq": "^5.7.0",
25
+ "ioredis": "^5.4.0",
26
+ "pino": "^9.1.0"
27
+ },
28
+ "devDependencies": {
29
+ "openship-tsconfig": "workspace:*",
30
+ "@types/jsonwebtoken": "^9.0.0",
31
+ "@types/bcryptjs": "^2.4.0",
32
+ "tsx": "^4.11.0",
33
+ "tsup": "^8.1.0",
34
+ "vitest": "^1.6.0",
35
+ "typescript": "^5.4.0"
36
+ }
37
+ }
@@ -0,0 +1,26 @@
1
+ import { Hono } from "hono";
2
+ import { cors } from "hono/cors";
3
+ import { logger } from "hono/logger";
4
+
5
+ import { authRoutes } from "./modules/auth/auth.routes";
6
+ import { projectRoutes } from "./modules/projects/project.routes";
7
+ import { deploymentRoutes } from "./modules/deployments/deployment.routes";
8
+ import { domainRoutes } from "./modules/domains/domain.routes";
9
+ import { billingRoutes } from "./modules/billing/billing.routes";
10
+ import { webhookRoutes } from "./modules/webhooks/webhook.routes";
11
+ import { healthRoutes } from "./modules/health/health.routes";
12
+
13
+ export const app = new Hono();
14
+
15
+ /* ---------- Global middleware ---------- */
16
+ app.use("*", logger());
17
+ app.use("*", cors());
18
+
19
+ /* ---------- Module routes ---------- */
20
+ app.route("/api/health", healthRoutes);
21
+ app.route("/api/auth", authRoutes);
22
+ app.route("/api/projects", projectRoutes);
23
+ app.route("/api/deployments", deploymentRoutes);
24
+ app.route("/api/domains", domainRoutes);
25
+ app.route("/api/billing", billingRoutes);
26
+ app.route("/api/webhooks", webhookRoutes);
@@ -0,0 +1,39 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * API configuration — loaded from environment variables.
5
+ *
6
+ * CLOUD_MODE=true enables billing, metering, and multi-tenant features.
7
+ * CLOUD_MODE=false (default) runs as a self-hosted single-tenant instance.
8
+ */
9
+ const envSchema = z.object({
10
+ NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
11
+ PORT: z.coerce.number().default(4000),
12
+
13
+ /* ---------- Mode ---------- */
14
+ CLOUD_MODE: z.coerce.boolean().default(false),
15
+
16
+ /* ---------- Database ---------- */
17
+ DATABASE_URL: z.string().default("file:./dev.db"),
18
+
19
+ /* ---------- Auth ---------- */
20
+ JWT_SECRET: z.string().default("change-me-in-production"),
21
+ JWT_EXPIRES_IN: z.string().default("15m"),
22
+ JWT_REFRESH_EXPIRES_IN: z.string().default("7d"),
23
+
24
+ /* ---------- Redis ---------- */
25
+ REDIS_URL: z.string().default("redis://localhost:6379"),
26
+
27
+ /* ---------- Stripe (Cloud only) ---------- */
28
+ STRIPE_SECRET_KEY: z.string().optional(),
29
+ STRIPE_WEBHOOK_SECRET: z.string().optional(),
30
+
31
+ /* ---------- Git Providers ---------- */
32
+ GITHUB_APP_ID: z.string().optional(),
33
+ GITHUB_PRIVATE_KEY: z.string().optional(),
34
+ GITHUB_WEBHOOK_SECRET: z.string().optional(),
35
+ });
36
+
37
+ export type Env = z.infer<typeof envSchema>;
38
+
39
+ export const env = envSchema.parse(process.env);
@@ -0,0 +1 @@
1
+ export { env } from "./env";
@@ -0,0 +1,8 @@
1
+ import { serve } from "@hono/node-server";
2
+ import { app } from "./app";
3
+
4
+ const port = Number(process.env.PORT) || 4000;
5
+
6
+ serve({ fetch: app.fetch, port }, (info) => {
7
+ console.log(`🚀 Openship API running on http://localhost:${info.port}`);
8
+ });
@@ -0,0 +1,24 @@
1
+ import type { Context, Next } from "hono";
2
+
3
+ /**
4
+ * JWT authentication middleware.
5
+ * Extracts and verifies the Bearer token from the Authorization header.
6
+ */
7
+ export async function authMiddleware(c: Context, next: Next) {
8
+ const header = c.req.header("Authorization");
9
+
10
+ if (!header?.startsWith("Bearer ")) {
11
+ return c.json({ error: "Unauthorized" }, 401);
12
+ }
13
+
14
+ const token = header.slice(7);
15
+
16
+ try {
17
+ // TODO: Verify JWT, attach user to context
18
+ // const payload = verifyToken(token);
19
+ // c.set("user", payload);
20
+ await next();
21
+ } catch {
22
+ return c.json({ error: "Invalid token" }, 401);
23
+ }
24
+ }
@@ -0,0 +1,21 @@
1
+ import type { Context, Next } from "hono";
2
+ import { ZodError } from "zod";
3
+
4
+ /**
5
+ * Global error handler middleware.
6
+ */
7
+ export async function errorHandler(c: Context, next: Next) {
8
+ try {
9
+ await next();
10
+ } catch (err) {
11
+ if (err instanceof ZodError) {
12
+ return c.json(
13
+ { error: "Validation error", details: err.flatten().fieldErrors },
14
+ 400,
15
+ );
16
+ }
17
+
18
+ console.error("[ERROR]", err);
19
+ return c.json({ error: "Internal server error" }, 500);
20
+ }
21
+ }
@@ -0,0 +1,3 @@
1
+ export { authMiddleware } from "./auth";
2
+ export { rateLimiter } from "./rate-limiter";
3
+ export { errorHandler } from "./error-handler";
@@ -0,0 +1,26 @@
1
+ import type { Context, Next } from "hono";
2
+
3
+ /**
4
+ * Rate-limiting middleware.
5
+ * Uses a simple in-memory store (swap for Redis in production).
6
+ */
7
+ const requestCounts = new Map<string, { count: number; resetAt: number }>();
8
+
9
+ export async function rateLimiter(c: Context, next: Next) {
10
+ const ip = c.req.header("x-forwarded-for") || "unknown";
11
+ const now = Date.now();
12
+ const window = 60_000; // 1 minute
13
+ const maxRequests = 100;
14
+
15
+ const entry = requestCounts.get(ip);
16
+
17
+ if (!entry || now > entry.resetAt) {
18
+ requestCounts.set(ip, { count: 1, resetAt: now + window });
19
+ } else if (entry.count >= maxRequests) {
20
+ return c.json({ error: "Too many requests" }, 429);
21
+ } else {
22
+ entry.count++;
23
+ }
24
+
25
+ await next();
26
+ }
@@ -0,0 +1,26 @@
1
+ import type { Context } from "hono";
2
+
3
+ export async function register(c: Context) {
4
+ // TODO: Implement registration with auth.service
5
+ return c.json({ message: "register" }, 201);
6
+ }
7
+
8
+ export async function login(c: Context) {
9
+ // TODO: Implement login
10
+ return c.json({ message: "login" });
11
+ }
12
+
13
+ export async function logout(c: Context) {
14
+ // TODO: Implement logout
15
+ return c.json({ message: "logout" });
16
+ }
17
+
18
+ export async function refresh(c: Context) {
19
+ // TODO: Implement token refresh
20
+ return c.json({ message: "refresh" });
21
+ }
22
+
23
+ export async function me(c: Context) {
24
+ // TODO: Return current user from JWT
25
+ return c.json({ message: "me" });
26
+ }
@@ -0,0 +1,10 @@
1
+ import { Hono } from "hono";
2
+ import * as authController from "./auth.controller";
3
+
4
+ export const authRoutes = new Hono();
5
+
6
+ authRoutes.post("/register", authController.register);
7
+ authRoutes.post("/login", authController.login);
8
+ authRoutes.post("/logout", authController.logout);
9
+ authRoutes.post("/refresh", authController.refresh);
10
+ authRoutes.get("/me", authController.me);
@@ -0,0 +1,12 @@
1
+ import { z } from "zod";
2
+
3
+ export const registerSchema = z.object({
4
+ email: z.string().email(),
5
+ password: z.string().min(8),
6
+ name: z.string().optional(),
7
+ });
8
+
9
+ export const loginSchema = z.object({
10
+ email: z.string().email(),
11
+ password: z.string(),
12
+ });
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Auth service — handles password hashing, JWT signing, user creation.
3
+ */
4
+
5
+ export async function createUser(data: {
6
+ email: string;
7
+ password: string;
8
+ name?: string;
9
+ }) {
10
+ // TODO: Hash password, insert into DB, return user
11
+ }
12
+
13
+ export async function verifyCredentials(email: string, password: string) {
14
+ // TODO: Fetch user, compare password hash
15
+ }
16
+
17
+ export async function generateTokens(userId: string) {
18
+ // TODO: Sign access + refresh JWTs
19
+ }
20
+
21
+ export async function refreshAccessToken(refreshToken: string) {
22
+ // TODO: Verify refresh token, return new access token
23
+ }
@@ -0,0 +1,60 @@
1
+ import type { Context } from "hono";
2
+
3
+ /* ---------- Plans ---------- */
4
+ export async function listPlans(c: Context) {
5
+ // TODO: Return available pricing plans (free, pro, team, enterprise)
6
+ return c.json({
7
+ data: [
8
+ { id: "free", name: "Free", price: 0, features: ["Self-hosted", "Community support"] },
9
+ { id: "pro", name: "Pro", price: 20, features: ["Cloud hosting", "Custom domains", "Priority support"] },
10
+ { id: "team", name: "Team", price: 50, features: ["All Pro features", "Team management", "SSO"] },
11
+ ],
12
+ });
13
+ }
14
+
15
+ /* ---------- Subscriptions ---------- */
16
+ export async function getSubscription(c: Context) {
17
+ return c.json({ data: null });
18
+ }
19
+
20
+ export async function createSubscription(c: Context) {
21
+ // TODO: Create Stripe checkout session
22
+ return c.json({ message: "subscription created" }, 201);
23
+ }
24
+
25
+ export async function updateSubscription(c: Context) {
26
+ // TODO: Upgrade/downgrade plan
27
+ return c.json({ message: "subscription updated" });
28
+ }
29
+
30
+ export async function cancelSubscription(c: Context) {
31
+ // TODO: Cancel at period end
32
+ return c.json({ message: "subscription cancelled" });
33
+ }
34
+
35
+ /* ---------- Usage ---------- */
36
+ export async function getUsage(c: Context) {
37
+ // TODO: Return current billing period usage (build minutes, bandwidth, etc.)
38
+ return c.json({ data: { buildMinutes: 0, bandwidth: 0 } });
39
+ }
40
+
41
+ /* ---------- Payment Methods ---------- */
42
+ export async function listPaymentMethods(c: Context) {
43
+ return c.json({ data: [] });
44
+ }
45
+
46
+ export async function addPaymentMethod(c: Context) {
47
+ // TODO: Create Stripe setup intent
48
+ return c.json({ message: "payment method added" }, 201);
49
+ }
50
+
51
+ /* ---------- Invoices ---------- */
52
+ export async function listInvoices(c: Context) {
53
+ return c.json({ data: [] });
54
+ }
55
+
56
+ /* ---------- Stripe Webhook ---------- */
57
+ export async function stripeWebhook(c: Context) {
58
+ // TODO: Verify Stripe signature, handle events
59
+ return c.json({ received: true });
60
+ }
@@ -0,0 +1,26 @@
1
+ import { Hono } from "hono";
2
+ import * as billingController from "./billing.controller";
3
+
4
+ export const billingRoutes = new Hono();
5
+
6
+ /* ---------- Plans & Pricing ---------- */
7
+ billingRoutes.get("/plans", billingController.listPlans);
8
+
9
+ /* ---------- Subscriptions ---------- */
10
+ billingRoutes.get("/subscription", billingController.getSubscription);
11
+ billingRoutes.post("/subscription", billingController.createSubscription);
12
+ billingRoutes.patch("/subscription", billingController.updateSubscription);
13
+ billingRoutes.delete("/subscription", billingController.cancelSubscription);
14
+
15
+ /* ---------- Usage ---------- */
16
+ billingRoutes.get("/usage", billingController.getUsage);
17
+
18
+ /* ---------- Payment Methods ---------- */
19
+ billingRoutes.get("/payment-methods", billingController.listPaymentMethods);
20
+ billingRoutes.post("/payment-methods", billingController.addPaymentMethod);
21
+
22
+ /* ---------- Invoices ---------- */
23
+ billingRoutes.get("/invoices", billingController.listInvoices);
24
+
25
+ /* ---------- Stripe Webhook (no auth) ---------- */
26
+ billingRoutes.post("/webhook/stripe", billingController.stripeWebhook);
@@ -0,0 +1,9 @@
1
+ import { z } from "zod";
2
+
3
+ export const createSubscriptionSchema = z.object({
4
+ planId: z.enum(["free", "pro", "team"]),
5
+ });
6
+
7
+ export const updateSubscriptionSchema = z.object({
8
+ planId: z.enum(["free", "pro", "team"]),
9
+ });