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.
- package/.env.example +22 -0
- package/.nvmrc +1 -0
- package/.prettierrc +7 -0
- package/CONTRIBUTING.md +40 -0
- package/README.md +164 -0
- package/apps/api/.env.example +30 -0
- package/apps/api/Dockerfile +24 -0
- package/apps/api/package.json +37 -0
- package/apps/api/src/app.ts +26 -0
- package/apps/api/src/config/env.ts +39 -0
- package/apps/api/src/config/index.ts +1 -0
- package/apps/api/src/index.ts +8 -0
- package/apps/api/src/middleware/auth.ts +24 -0
- package/apps/api/src/middleware/error-handler.ts +21 -0
- package/apps/api/src/middleware/index.ts +3 -0
- package/apps/api/src/middleware/rate-limiter.ts +26 -0
- package/apps/api/src/modules/auth/auth.controller.ts +26 -0
- package/apps/api/src/modules/auth/auth.routes.ts +10 -0
- package/apps/api/src/modules/auth/auth.schema.ts +12 -0
- package/apps/api/src/modules/auth/auth.service.ts +23 -0
- package/apps/api/src/modules/billing/billing.controller.ts +60 -0
- package/apps/api/src/modules/billing/billing.routes.ts +26 -0
- package/apps/api/src/modules/billing/billing.schema.ts +9 -0
- package/apps/api/src/modules/billing/billing.service.ts +33 -0
- package/apps/api/src/modules/deployments/deployment.controller.ts +35 -0
- package/apps/api/src/modules/deployments/deployment.routes.ts +11 -0
- package/apps/api/src/modules/deployments/deployment.schema.ts +7 -0
- package/apps/api/src/modules/deployments/deployment.service.ts +23 -0
- package/apps/api/src/modules/domains/domain.controller.ts +21 -0
- package/apps/api/src/modules/domains/domain.routes.ts +9 -0
- package/apps/api/src/modules/domains/domain.service.ts +19 -0
- package/apps/api/src/modules/health/health.routes.ts +10 -0
- package/apps/api/src/modules/projects/project.controller.ts +29 -0
- package/apps/api/src/modules/projects/project.routes.ts +10 -0
- package/apps/api/src/modules/projects/project.schema.ts +9 -0
- package/apps/api/src/modules/projects/project.service.ts +23 -0
- package/apps/api/src/modules/webhooks/webhook.controller.ts +22 -0
- package/apps/api/src/modules/webhooks/webhook.routes.ts +12 -0
- package/apps/api/src/modules/webhooks/webhook.service.ts +11 -0
- package/apps/api/tsconfig.json +12 -0
- package/apps/cli/package.json +28 -0
- package/apps/cli/src/commands/deploy.ts +11 -0
- package/apps/cli/src/commands/init.ts +8 -0
- package/apps/cli/src/commands/login.ts +8 -0
- package/apps/cli/src/commands/logs.ts +9 -0
- package/apps/cli/src/index.ts +21 -0
- package/apps/cli/src/lib/api-client.ts +35 -0
- package/apps/cli/tsconfig.json +12 -0
- package/apps/dashboard/Dockerfile +24 -0
- package/apps/dashboard/next.config.mjs +6 -0
- package/apps/dashboard/package.json +28 -0
- package/apps/dashboard/src/app/(auth)/layout.tsx +6 -0
- package/apps/dashboard/src/app/(auth)/login/page.tsx +11 -0
- package/apps/dashboard/src/app/(auth)/register/page.tsx +10 -0
- package/apps/dashboard/src/app/(dashboard)/billing/page.tsx +10 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/page.tsx +8 -0
- package/apps/dashboard/src/app/(dashboard)/domains/page.tsx +8 -0
- package/apps/dashboard/src/app/(dashboard)/layout.tsx +37 -0
- package/apps/dashboard/src/app/(dashboard)/monitoring/page.tsx +8 -0
- package/apps/dashboard/src/app/(dashboard)/projects/page.tsx +8 -0
- package/apps/dashboard/src/app/(dashboard)/settings/page.tsx +8 -0
- package/apps/dashboard/src/app/globals.css +3 -0
- package/apps/dashboard/src/app/layout.tsx +15 -0
- package/apps/dashboard/src/app/page.tsx +6 -0
- package/apps/dashboard/tsconfig.json +11 -0
- package/apps/web/Dockerfile +24 -0
- package/apps/web/next.config.mjs +6 -0
- package/apps/web/package.json +26 -0
- package/apps/web/src/app/(marketing)/docs/page.tsx +10 -0
- package/apps/web/src/app/(marketing)/layout.tsx +24 -0
- package/apps/web/src/app/(marketing)/page.tsx +2 -0
- package/apps/web/src/app/(marketing)/pricing/page.tsx +10 -0
- package/apps/web/src/app/globals.css +3 -0
- package/apps/web/src/app/layout.tsx +16 -0
- package/apps/web/src/app/page.tsx +10 -0
- package/apps/web/tsconfig.json +11 -0
- package/docker-compose.yml +87 -0
- package/package.json +31 -0
- package/packages/adapters/package.json +27 -0
- package/packages/adapters/src/base-adapter.ts +49 -0
- package/packages/adapters/src/docker-adapter.ts +43 -0
- package/packages/adapters/src/index.ts +4 -0
- package/packages/adapters/src/oblien-adapter.ts +46 -0
- package/packages/adapters/src/registry.ts +22 -0
- package/packages/adapters/tsconfig.json +9 -0
- package/packages/core/package.json +27 -0
- package/packages/core/src/constants.ts +28 -0
- package/packages/core/src/errors.ts +49 -0
- package/packages/core/src/index.ts +4 -0
- package/packages/core/src/types.ts +47 -0
- package/packages/core/src/utils.ts +35 -0
- package/packages/core/tsconfig.json +9 -0
- package/packages/db/package.json +32 -0
- package/packages/db/prisma/schema.prisma +160 -0
- package/packages/db/src/index.ts +11 -0
- package/packages/db/tsconfig.json +9 -0
- package/packages/ui/package.json +38 -0
- package/packages/ui/src/components/badge.tsx +29 -0
- package/packages/ui/src/components/button.tsx +44 -0
- package/packages/ui/src/components/card.tsx +26 -0
- package/packages/ui/src/components/status-dot.tsx +25 -0
- package/packages/ui/src/globals.css +18 -0
- package/packages/ui/src/index.tsx +8 -0
- package/packages/ui/src/lib/cn.ts +7 -0
- package/packages/ui/tsconfig.json +9 -0
- package/pnpm-workspace.yaml +4 -0
- package/tooling/tsconfig/base.json +19 -0
- package/tooling/tsconfig/nextjs.json +10 -0
- package/tooling/tsconfig/node.json +10 -0
- package/tooling/tsconfig/package.json +7 -0
- 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
package/CONTRIBUTING.md
ADDED
|
@@ -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,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,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);
|