create-softeneers-app 0.2.2 → 0.2.4
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/README.html +4 -4
- package/README.md +6 -6
- package/dist/args.js +23 -2
- package/dist/args.js.map +1 -1
- package/dist/fragments.js +1 -1
- package/dist/fragments.js.map +1 -1
- package/dist/index.js +10 -7
- package/dist/index.js.map +1 -1
- package/dist/prompts.js +12 -2
- package/dist/prompts.js.map +1 -1
- package/package.json +1 -1
- package/templates/express-api/.env.example +23 -0
- package/templates/express-api/README.md +68 -1
- package/templates/express-api/package.json +3 -0
- package/templates/express-api/softeneers.template.json +20 -1
- package/templates/express-api/src/email/mailer.ts +6 -0
- package/templates/express-api/src/email/routes.ts +26 -0
- package/templates/express-api/src/env.ts +18 -0
- package/templates/express-api/src/index.ts +23 -0
- package/templates/express-api/src/payments/routes.ts +44 -0
- package/templates/express-api/src/payments/stripe.ts +6 -0
- package/templates/express-api/src/payments/webhook.ts +43 -0
- package/templates/express-api/src/storage/routes.ts +28 -0
- package/templates/express-api/src/storage/store.ts +13 -0
- package/templates/hono-api/.env.example +23 -0
- package/templates/hono-api/README.md +69 -2
- package/templates/hono-api/package.json +3 -0
- package/templates/hono-api/softeneers.template.json +20 -1
- package/templates/hono-api/src/email/mailer.ts +6 -0
- package/templates/hono-api/src/email/routes.ts +24 -0
- package/templates/hono-api/src/env.ts +18 -0
- package/templates/hono-api/src/index.ts +20 -0
- package/templates/hono-api/src/payments/routes.ts +41 -0
- package/templates/hono-api/src/payments/stripe.ts +6 -0
- package/templates/hono-api/src/payments/webhook.ts +36 -0
- package/templates/hono-api/src/storage/routes.ts +29 -0
- package/templates/hono-api/src/storage/store.ts +13 -0
- package/templates/next-fullstack/apps/server/package.json +1 -0
- package/templates/next-fullstack/apps/web/package.json +1 -0
- package/templates/next-fullstack/package.json +1 -0
- package/templates/next-fullstack/turbo.json +3 -0
- package/templates/tanstack-start/.env.example +23 -0
- package/templates/tanstack-start/README.md +58 -3
- package/templates/tanstack-start/package.json +4 -0
- package/templates/tanstack-start/softeneers.template.json +28 -3
- package/templates/tanstack-start/src/lib/auth-client.ts +4 -0
- package/templates/tanstack-start/src/routes/account.tsx +47 -0
- package/templates/tanstack-start/src/routes/api/webhooks/stripe.ts +43 -0
- package/templates/tanstack-start/src/routes/billing.tsx +59 -0
- package/templates/tanstack-start/src/routes/index.tsx +15 -6
- package/templates/tanstack-start/src/routes/login.tsx +77 -0
- package/templates/tanstack-start/src/server/email.ts +27 -0
- package/templates/tanstack-start/src/server/env.ts +18 -0
- package/templates/tanstack-start/src/server/payments.ts +40 -0
- package/templates/tanstack-start/src/server/storage.ts +36 -0
package/README.html
CHANGED
|
@@ -38,15 +38,15 @@ footer { margin-top: 3rem; padding-top: 1rem; border-top: 1px solid #8884; font-
|
|
|
38
38
|
<p>Five templates, each for a different kind of user, with composable toggles:</p>
|
|
39
39
|
<table><thead><tr><th>Template</th><th>What you get</th><th>Toggles</th></tr></thead><tbody>
|
|
40
40
|
<tr><td><code>next-fullstack</code></td><td>Next.js web + Express/Sequelize/MySQL API</td><td>(all-in)</td></tr>
|
|
41
|
-
<tr><td><code>express-api</code></td><td>Express 5 + TypeScript REST API (cars CRUD)</td><td>db · auth · docker</td></tr>
|
|
42
|
-
<tr><td><code>hono-api</code></td><td>Hono + TypeScript API (cars CRUD)</td><td>db · auth · docker</td></tr>
|
|
43
|
-
<tr><td><code>tanstack-start</code></td><td>TanStack Start fullstack React app</td><td>db · auth · docker</td></tr>
|
|
41
|
+
<tr><td><code>express-api</code></td><td>Express 5 + TypeScript REST API (cars CRUD)</td><td>db · auth · docker · email · storage · payments</td></tr>
|
|
42
|
+
<tr><td><code>hono-api</code></td><td>Hono + TypeScript API (cars CRUD)</td><td>db · auth · docker · email · storage · payments</td></tr>
|
|
43
|
+
<tr><td><code>tanstack-start</code></td><td>TanStack Start fullstack React app (+ auth/billing UI)</td><td>db · auth · docker · email · storage · payments</td></tr>
|
|
44
44
|
<tr><td><code>minimal</code></td><td>Zero-framework Node + TypeScript starter</td><td>(none)</td></tr>
|
|
45
45
|
</tbody></table>
|
|
46
46
|
<pre><code>npx create-softeneers-app@latest my-app # interactive
|
|
47
47
|
npx create-softeneers-app@latest api -t express-api --yes # all defaults
|
|
48
48
|
npx create-softeneers-app@latest api -t hono-api --no-auth --no-docker</code></pre>
|
|
49
|
-
<p>Flags: <code>--template <slug></code>, <code>--yes</code>/<code>-y</code>, <code>--db</code
|
|
49
|
+
<p>Flags: <code>--template <slug></code>, <code>--yes</code>/<code>-y</code>, <code>--db</code>, <code>--auth</code>, <code>--docker</code>, <code>--email</code>, <code>--storage</code>, <code>--payments</code> (each with a <code>--no-*</code> form), <code>--no-install</code>, <code>--no-git</code>, <code>--pm <pnpm|npm|yarn></code>, <code>--help</code>, <code>--version</code>. See <a href="../../docs/CLI-SPEC.html"><code>../../docs/CLI-SPEC.md</code></a> for the full contract.</p>
|
|
50
50
|
<h2>Local development</h2>
|
|
51
51
|
<pre><code>npm run build -w create-softeneers-app # tsc + bundle templates → dist/ + templates/
|
|
52
52
|
node apps/cli/dist/index.js my-app --yes # run the built CLI
|
package/README.md
CHANGED
|
@@ -14,9 +14,9 @@ Five templates, each for a different kind of user, with composable toggles:
|
|
|
14
14
|
| Template | What you get | Toggles |
|
|
15
15
|
| ---------------- | ----------------------------------------- | ------------------ |
|
|
16
16
|
| `next-fullstack` | Next.js web + Express/Sequelize/MySQL API | (all-in) |
|
|
17
|
-
| `express-api` | Express 5 + TypeScript REST API (cars CRUD) | db · auth · docker |
|
|
18
|
-
| `hono-api` | Hono + TypeScript API (cars CRUD) | db · auth · docker |
|
|
19
|
-
| `tanstack-start` | TanStack Start fullstack React app
|
|
17
|
+
| `express-api` | Express 5 + TypeScript REST API (cars CRUD) | db · auth · docker · email · storage · payments |
|
|
18
|
+
| `hono-api` | Hono + TypeScript API (cars CRUD) | db · auth · docker · email · storage · payments |
|
|
19
|
+
| `tanstack-start` | TanStack Start fullstack React app (+ auth/billing UI) | db · auth · docker · email · storage · payments |
|
|
20
20
|
| `minimal` | Zero-framework Node + TypeScript starter | (none) |
|
|
21
21
|
|
|
22
22
|
```bash
|
|
@@ -25,9 +25,9 @@ npx create-softeneers-app@latest api -t express-api --yes # all defaults
|
|
|
25
25
|
npx create-softeneers-app@latest api -t hono-api --no-auth --no-docker
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
Flags: `--template <slug>`, `--yes`/`-y`, `--db
|
|
29
|
-
`--
|
|
30
|
-
`--help`, `--version`. See
|
|
28
|
+
Flags: `--template <slug>`, `--yes`/`-y`, `--db`, `--auth`, `--docker`, `--email`,
|
|
29
|
+
`--storage`, `--payments` (each with a `--no-*` form), `--no-install`, `--no-git`,
|
|
30
|
+
`--pm <pnpm|npm|yarn>`, `--help`, `--version`. See
|
|
31
31
|
[`../../docs/CLI-SPEC.md`](../../docs/CLI-SPEC.md) for the full contract.
|
|
32
32
|
|
|
33
33
|
## Local development
|
package/dist/args.js
CHANGED
|
@@ -54,6 +54,24 @@ export function parseArgs(argv) {
|
|
|
54
54
|
case "--no-docker":
|
|
55
55
|
opts.docker = false;
|
|
56
56
|
break;
|
|
57
|
+
case "--email":
|
|
58
|
+
opts.email = true;
|
|
59
|
+
break;
|
|
60
|
+
case "--no-email":
|
|
61
|
+
opts.email = false;
|
|
62
|
+
break;
|
|
63
|
+
case "--storage":
|
|
64
|
+
opts.storage = true;
|
|
65
|
+
break;
|
|
66
|
+
case "--no-storage":
|
|
67
|
+
opts.storage = false;
|
|
68
|
+
break;
|
|
69
|
+
case "--payments":
|
|
70
|
+
opts.payments = true;
|
|
71
|
+
break;
|
|
72
|
+
case "--no-payments":
|
|
73
|
+
opts.payments = false;
|
|
74
|
+
break;
|
|
57
75
|
case "--help":
|
|
58
76
|
case "-h":
|
|
59
77
|
opts.help = true;
|
|
@@ -101,8 +119,11 @@ Options:
|
|
|
101
119
|
-t, --template <name> Template to use (default: prompt)
|
|
102
120
|
-y, --yes Accept defaults, no prompts
|
|
103
121
|
--db / --no-db Include a database layer (template default if unset)
|
|
104
|
-
--auth / --no-auth Include authentication (template default if unset)
|
|
105
|
-
--docker / --no-docker
|
|
122
|
+
--auth / --no-auth Include authentication + UI (template default if unset)
|
|
123
|
+
--docker / --no-docker Include a Docker recipe
|
|
124
|
+
--email / --no-email Include transactional email (@softeneers/email)
|
|
125
|
+
--storage / --no-storage Include object storage (@softeneers/storage)
|
|
126
|
+
--payments / --no-payments Include Stripe payments (@softeneers/payments)
|
|
106
127
|
--no-install Don't install dependencies
|
|
107
128
|
--no-git Don't run "git init"
|
|
108
129
|
--pm <npm|pnpm|yarn> Package manager (default: auto-detected, else npm)
|
package/dist/args.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAAA,uEAAuE;
|
|
1
|
+
{"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"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"}
|
package/dist/fragments.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// without a manifest (e.g. `minimal`) support no toggles and are copied as-is.
|
|
6
6
|
import { basename, join } from "node:path";
|
|
7
7
|
import { existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
-
export const TOGGLE_KEYS = ["db", "auth", "docker"];
|
|
8
|
+
export const TOGGLE_KEYS = ["db", "auth", "docker", "email", "storage", "payments"];
|
|
9
9
|
const MANIFEST_NAME = "softeneers.template.json";
|
|
10
10
|
export function readManifest(dir) {
|
|
11
11
|
const p = join(dir, MANIFEST_NAME);
|
package/dist/fragments.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fragments.js","sourceRoot":"","sources":["../src/fragments.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,+EAA+E;AAC/E,iFAAiF;AACjF,8EAA8E;AAC9E,+EAA+E;AAC/E,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAEvF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"fragments.js","sourceRoot":"","sources":["../src/fragments.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
import { basename, resolve } from "node:path";
|
|
4
4
|
import { intro, log, note, outro, spinner } from "@clack/prompts";
|
|
5
5
|
import { CliError, HELP_TEXT, parseArgs } from "./args.js";
|
|
6
|
-
import { applyToggles, templateToggleDefaults } from "./fragments.js";
|
|
6
|
+
import { applyToggles, TOGGLE_KEYS, templateToggleDefaults } from "./fragments.js";
|
|
7
7
|
import { promptProjectName, promptTemplate, promptToggles } from "./prompts.js";
|
|
8
8
|
import { assertTargetUsable, copyTemplate, gitInit, installDeps, toPackageName, transform, } from "./scaffold.js";
|
|
9
9
|
import { DEFAULT_TEMPLATE, findTemplate, resolveTemplateDir } from "./templates.js";
|
|
@@ -47,13 +47,16 @@ async function main() {
|
|
|
47
47
|
}
|
|
48
48
|
// 3. Feature toggles (db / auth / docker) — only those the template supports.
|
|
49
49
|
const toggleDefaults = templateToggleDefaults(templateDir);
|
|
50
|
-
const overrides = {
|
|
50
|
+
const overrides = {
|
|
51
|
+
db: opts.db,
|
|
52
|
+
auth: opts.auth,
|
|
53
|
+
docker: opts.docker,
|
|
54
|
+
email: opts.email,
|
|
55
|
+
storage: opts.storage,
|
|
56
|
+
payments: opts.payments,
|
|
57
|
+
};
|
|
51
58
|
const toggles = opts.yes
|
|
52
|
-
?
|
|
53
|
-
db: overrides.db ?? toggleDefaults.db ?? false,
|
|
54
|
-
auth: overrides.auth ?? toggleDefaults.auth ?? false,
|
|
55
|
-
docker: overrides.docker ?? toggleDefaults.docker ?? false,
|
|
56
|
-
}
|
|
59
|
+
? Object.fromEntries(TOGGLE_KEYS.map((key) => [key, overrides[key] ?? toggleDefaults[key] ?? false]))
|
|
57
60
|
: await promptToggles(toggleDefaults, overrides);
|
|
58
61
|
// 4. Generate.
|
|
59
62
|
assertTargetUsable(targetDir);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE9C,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAElE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAgB,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE9C,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAElE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,WAAW,EAAgB,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACjG,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAChF,OAAO,EACL,kBAAkB,EAClB,YAAY,EACZ,OAAO,EACP,WAAW,EACX,aAAa,EACb,SAAS,GACV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEpF,SAAS,OAAO;IACd,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/C,OAAQ,OAAO,CAAC,iBAAiB,CAAyB,CAAC,OAAO,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAE9C,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACvB,OAAO;IACT,CAAC;IACD,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACvB,OAAO;IACT,CAAC;IAED,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAE/B,6FAA6F;IAC7F,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,iBAAiB,EAAE,CAAC,CAAC;IACtF,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,CAAC,CAAC;IACpD,MAAM,YAAY,GAAG,SAAS,KAAK,OAAO,CAAC,GAAG,EAAE,CAAC;IACjD,MAAM,WAAW,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;IAExC,eAAe;IACf,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,MAAM,cAAc,EAAE,CAAC,CAAC;IACrF,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,QAAQ,CAAC,qBAAqB,IAAI,IAAI,CAAC,CAAC;IACpD,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,QAAQ,CAAC,aAAa,IAAI,+CAA+C,CAAC,CAAC;IACvF,CAAC;IACD,MAAM,WAAW,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,QAAQ,CAAC,wCAAwC,IAAI,IAAI,CAAC,CAAC;IACvE,CAAC;IAED,8EAA8E;IAC9E,MAAM,cAAc,GAAG,sBAAsB,CAAC,WAAW,CAAC,CAAC;IAC3D,MAAM,SAAS,GAAqB;QAClC,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;KACxB,CAAC;IACF,MAAM,OAAO,GAAY,IAAI,CAAC,GAAG;QAC/B,CAAC,CAAE,MAAM,CAAC,WAAW,CACjB,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC,IAAI,cAAc,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,CACpE;QACf,CAAC,CAAC,MAAM,aAAa,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;IAEnD,eAAe;IACf,kBAAkB,CAAC,SAAS,CAAC,CAAC;IAC9B,MAAM,OAAO,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;IAEzC,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC;IACpB,CAAC,CAAC,KAAK,CAAC,YAAY,WAAW,EAAE,CAAC,CAAC;IACnC,YAAY,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;IACrC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,CAAC,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;IAChE,CAAC,CAAC,IAAI,CAAC,WAAW,WAAW,EAAE,CAAC,CAAC;IAEjC,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;QACb,IAAI,OAAO,CAAC,SAAS,CAAC;YAAE,GAAG,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAC;;YAChE,GAAG,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;IACzD,CAAC;IAED,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;QACvB,IAAI,CAAC,KAAK,CAAC,gCAAgC,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC;QAClE,IAAI,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC;YAChD,IAAI,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACvC,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IAED,oEAAoE;IACpE,MAAM,EAAE,GAAG,IAAI,CAAC,cAAc,CAAC;IAC/B,MAAM,KAAK,GAAG;QACZ,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,SAAS,EAAE,CAAC,CAAC;QAC5C,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;QAC1C,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,8CAA8C,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3E,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,iBAAiB,EAAE,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACpE,GAAG,EAAE,UAAU;KAChB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,IAAI,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;IAC1B,KAAK,CAAC,QAAQ,CAAC,KAAK,IAAI,gBAAgB,CAAC,CAAC;AAC5C,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC5B,IAAI,GAAG,YAAY,QAAQ,EAAE,CAAC;QAC5B,OAAO,CAAC,KAAK,CAAC,YAAY,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACzC,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,KAAK,CAAC,qBAAqB,EAAE,GAAG,CAAC,CAAC;QAC1C,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC;AACH,CAAC,CAAC,CAAC"}
|
package/dist/prompts.js
CHANGED
|
@@ -31,8 +31,11 @@ export async function promptTemplate() {
|
|
|
31
31
|
}
|
|
32
32
|
const TOGGLE_LABELS = {
|
|
33
33
|
db: "Include a database? (CRUD persisted to MySQL via Sequelize)",
|
|
34
|
-
auth: "Include authentication? (email + password via better-auth)",
|
|
34
|
+
auth: "Include authentication? (email + password via better-auth, with UI)",
|
|
35
35
|
docker: "Include a Docker recipe? (docker-compose for local services)",
|
|
36
|
+
email: "Include transactional email? (Resend + React Email)",
|
|
37
|
+
storage: "Include object storage? (S3 / R2 / MinIO uploads)",
|
|
38
|
+
payments: "Include Stripe payments? (checkout, subscriptions, webhooks)",
|
|
36
39
|
};
|
|
37
40
|
/**
|
|
38
41
|
* Ask for each toggle the template supports (a key present in `defaults`).
|
|
@@ -40,7 +43,14 @@ const TOGGLE_LABELS = {
|
|
|
40
43
|
* skips its prompt. Unsupported toggles resolve to `false`.
|
|
41
44
|
*/
|
|
42
45
|
export async function promptToggles(defaults, overrides) {
|
|
43
|
-
const result = {
|
|
46
|
+
const result = {
|
|
47
|
+
db: false,
|
|
48
|
+
auth: false,
|
|
49
|
+
docker: false,
|
|
50
|
+
email: false,
|
|
51
|
+
storage: false,
|
|
52
|
+
payments: false,
|
|
53
|
+
};
|
|
44
54
|
for (const key of Object.keys(defaults)) {
|
|
45
55
|
if (overrides[key] !== undefined) {
|
|
46
56
|
result[key] = overrides[key];
|
package/dist/prompts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEzE,OAAO,EAAgC,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE7D,SAAS,IAAI,CAAI,KAAiB;IAChC,IAAI,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACpB,MAAM,CAAC,YAAY,CAAC,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,KAAU,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAAgB;IACtD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC;QACvB,OAAO,EAAE,eAAe;QACxB,WAAW,EAAE,QAAQ;QACrB,YAAY,EAAE,OAAO;QACrB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,8BAA8B,CAAC,CAAC,CAAC,SAAS,CAAC;KACtF,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC;QACzB,OAAO,EAAE,mCAAmC;QAC5C,YAAY,EAAE,gBAAgB;QAC9B,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC7B,KAAK,EAAE,CAAC,CAAC,IAAI;YACb,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,sBAAsB;SAC7D,CAAC,CAAC;KACJ,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC;AACrB,CAAC;AAED,MAAM,aAAa,GAA8B;IAC/C,EAAE,EAAE,6DAA6D;IACjE,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEzE,OAAO,EAAgC,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE7D,SAAS,IAAI,CAAI,KAAiB;IAChC,IAAI,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACpB,MAAM,CAAC,YAAY,CAAC,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,KAAU,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAAgB;IACtD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC;QACvB,OAAO,EAAE,eAAe;QACxB,WAAW,EAAE,QAAQ;QACrB,YAAY,EAAE,OAAO;QACrB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,8BAA8B,CAAC,CAAC,CAAC,SAAS,CAAC;KACtF,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC;QACzB,OAAO,EAAE,mCAAmC;QAC5C,YAAY,EAAE,gBAAgB;QAC9B,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC7B,KAAK,EAAE,CAAC,CAAC,IAAI;YACb,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,sBAAsB;SAC7D,CAAC,CAAC;KACJ,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC;AACrB,CAAC;AAED,MAAM,aAAa,GAA8B;IAC/C,EAAE,EAAE,6DAA6D;IACjE,IAAI,EAAE,qEAAqE;IAC3E,MAAM,EAAE,8DAA8D;IACtE,KAAK,EAAE,qDAAqD;IAC5D,OAAO,EAAE,mDAAmD;IAC5D,QAAQ,EAAE,8DAA8D;CACzE,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAA0B,EAC1B,SAA2B;IAE3B,MAAM,MAAM,GAAY;QACtB,EAAE,EAAE,KAAK;QACT,IAAI,EAAE,KAAK;QACX,MAAM,EAAE,KAAK;QACb,KAAK,EAAE,KAAK;QACZ,OAAO,EAAE,KAAK;QACd,QAAQ,EAAE,KAAK;KAChB,CAAC;IACF,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAgB,EAAE,CAAC;QACvD,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,SAAS,EAAE,CAAC;YACjC,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,GAAG,CAAE,CAAC;YAC9B,SAAS;QACX,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC;YAC1B,OAAO,EAAE,aAAa,CAAC,GAAG,CAAC;YAC3B,YAAY,EAAE,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK;SACrC,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-softeneers-app",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Scaffold a new Softeneers Framework project: 5 templates (next-fullstack, express-api, hono-api, tanstack-start, minimal) with db/auth/docker toggles.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,3 +11,26 @@ DB_PASSWORD=
|
|
|
11
11
|
AUTH_SECRET=dev-secret-change-me-to-a-long-random-string
|
|
12
12
|
AUTH_BASE_URL=http://localhost:4000
|
|
13
13
|
# #endif
|
|
14
|
+
# #if email
|
|
15
|
+
# Get a key at https://resend.com — the app boots without it; sending needs it.
|
|
16
|
+
RESEND_API_KEY=re_set_me
|
|
17
|
+
EMAIL_FROM=onboarding@resend.dev
|
|
18
|
+
# #endif
|
|
19
|
+
# #if storage
|
|
20
|
+
# S3-compatible storage (AWS S3 / Cloudflare R2 / MinIO).
|
|
21
|
+
S3_ACCESS_KEY_ID=
|
|
22
|
+
S3_SECRET_ACCESS_KEY=
|
|
23
|
+
S3_BUCKET=uploads
|
|
24
|
+
S3_REGION=auto
|
|
25
|
+
S3_ENDPOINT=
|
|
26
|
+
# #endif
|
|
27
|
+
# #if payments
|
|
28
|
+
# Stripe — the ONLY thing you must set for payments to work. Test keys from
|
|
29
|
+
# https://dashboard.stripe.com/test/apikeys ; webhook secret from `stripe listen`
|
|
30
|
+
# or the dashboard. Create two Prices and paste their ids below.
|
|
31
|
+
APP_URL=http://localhost:4000
|
|
32
|
+
STRIPE_SECRET_KEY=sk_test_set_me
|
|
33
|
+
STRIPE_WEBHOOK_SECRET=whsec_set_me
|
|
34
|
+
STRIPE_PRICE_ID=price_set_me
|
|
35
|
+
STRIPE_SUBSCRIPTION_PRICE_ID=price_set_me
|
|
36
|
+
# #endif
|
|
@@ -32,6 +32,20 @@ npm run db:reset # drop, recreate, reseed
|
|
|
32
32
|
# #if auth
|
|
33
33
|
| ALL | `/api/auth/*` | better-auth routes |
|
|
34
34
|
# #endif
|
|
35
|
+
# #if email
|
|
36
|
+
| POST | `/api/email/welcome` | Send a welcome email |
|
|
37
|
+
# #endif
|
|
38
|
+
# #if storage
|
|
39
|
+
| POST | `/api/files/:key` | Upload an object |
|
|
40
|
+
| GET | `/api/files/:key/url`| Signed download URL |
|
|
41
|
+
| DELETE | `/api/files/:key` | Delete an object |
|
|
42
|
+
# #endif
|
|
43
|
+
# #if payments
|
|
44
|
+
| POST | `/api/payments/checkout` | One-time Stripe checkout |
|
|
45
|
+
| POST | `/api/payments/subscribe` | Subscription checkout |
|
|
46
|
+
| POST | `/api/payments/portal` | Billing portal |
|
|
47
|
+
| POST | `/api/webhooks/stripe` | Stripe webhook (verified) |
|
|
48
|
+
# #endif
|
|
35
49
|
|
|
36
50
|
```bash
|
|
37
51
|
curl localhost:4000/api/cars
|
|
@@ -61,7 +75,60 @@ npm run db:migrate && npm run db:seed
|
|
|
61
75
|
## Authentication
|
|
62
76
|
|
|
63
77
|
Email + password auth via better-auth ([`@softeneers/auth`](https://www.npmjs.com/package/@softeneers/auth)),
|
|
64
|
-
mounted at `/api/auth
|
|
78
|
+
mounted at `/api/auth/*` (`sign-up/email`, `sign-in/email`, `get-session`, …).
|
|
79
|
+
Set a strong `AUTH_SECRET` in `.env` before deploying.
|
|
80
|
+
# #endif
|
|
81
|
+
# #if email
|
|
82
|
+
|
|
83
|
+
## Email
|
|
84
|
+
|
|
85
|
+
Transactional email via Resend ([`@softeneers/email`](https://www.npmjs.com/package/@softeneers/email)).
|
|
86
|
+
Set `RESEND_API_KEY` (and `EMAIL_FROM`) in `.env`, then:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
curl -X POST localhost:4000/api/email/welcome -H 'content-type: application/json' \
|
|
90
|
+
-d '{"to":"you@example.com","name":"Ada"}'
|
|
91
|
+
```
|
|
92
|
+
# #endif
|
|
93
|
+
# #if storage
|
|
94
|
+
|
|
95
|
+
## Storage
|
|
96
|
+
|
|
97
|
+
S3-compatible uploads ([`@softeneers/storage`](https://www.npmjs.com/package/@softeneers/storage))
|
|
98
|
+
— works with AWS S3, Cloudflare R2, and MinIO. Set the `S3_*` keys in `.env`, then:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
curl -X POST --data-binary @photo.png localhost:4000/api/files/photo.png
|
|
102
|
+
curl localhost:4000/api/files/photo.png/url # → a signed download URL
|
|
103
|
+
```
|
|
104
|
+
# #endif
|
|
105
|
+
# #if payments
|
|
106
|
+
|
|
107
|
+
## Payments (Stripe)
|
|
108
|
+
|
|
109
|
+
Stripe checkout, subscriptions, the billing portal, and a verified webhook
|
|
110
|
+
([`@softeneers/payments`](https://www.npmjs.com/package/@softeneers/payments)) —
|
|
111
|
+
**all pre-wired**. The only thing you do is paste your keys into `.env`:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
STRIPE_SECRET_KEY=sk_test_…
|
|
115
|
+
STRIPE_WEBHOOK_SECRET=whsec_…
|
|
116
|
+
STRIPE_PRICE_ID=price_… # one-time price
|
|
117
|
+
STRIPE_SUBSCRIPTION_PRICE_ID=price_… # recurring price
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Get test keys at <https://dashboard.stripe.com/test/apikeys> and create two
|
|
121
|
+
Prices. Locally, forward webhooks with the Stripe CLI (this also prints the
|
|
122
|
+
`whsec_…` secret):
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
stripe listen --forward-to localhost:4000/api/webhooks/stripe
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Then `POST /api/payments/checkout` (or `/subscribe`) returns a Stripe URL to
|
|
129
|
+
redirect the customer to. The webhook handles `checkout.session.completed` and
|
|
130
|
+
the `customer.subscription.*` events — add your fulfilment logic in
|
|
131
|
+
`src/payments/webhook.ts`.
|
|
65
132
|
# #endif
|
|
66
133
|
|
|
67
134
|
## Getting started
|
|
@@ -20,7 +20,10 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@softeneers/auth": "^0.1.0",
|
|
22
22
|
"@softeneers/db": "^0.1.0",
|
|
23
|
+
"@softeneers/email": "^0.1.0",
|
|
23
24
|
"@softeneers/env": "^0.1.0",
|
|
25
|
+
"@softeneers/payments": "^0.1.0",
|
|
26
|
+
"@softeneers/storage": "^0.1.0",
|
|
24
27
|
"cors": "^2.8.5",
|
|
25
28
|
"dotenv": "^16.4.5",
|
|
26
29
|
"express": "^5.0.0",
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
-
"toggles": {
|
|
2
|
+
"toggles": {
|
|
3
|
+
"db": false,
|
|
4
|
+
"auth": false,
|
|
5
|
+
"docker": false,
|
|
6
|
+
"email": false,
|
|
7
|
+
"storage": false,
|
|
8
|
+
"payments": false
|
|
9
|
+
},
|
|
3
10
|
"fragments": {
|
|
4
11
|
"db": {
|
|
5
12
|
"removePaths": ["src/db.ts", "src/scripts", "docker-compose.yml"],
|
|
@@ -12,6 +19,18 @@
|
|
|
12
19
|
},
|
|
13
20
|
"docker": {
|
|
14
21
|
"removePaths": ["docker-compose.yml"]
|
|
22
|
+
},
|
|
23
|
+
"email": {
|
|
24
|
+
"removePaths": ["src/email"],
|
|
25
|
+
"removeDeps": ["@softeneers/email"]
|
|
26
|
+
},
|
|
27
|
+
"storage": {
|
|
28
|
+
"removePaths": ["src/storage"],
|
|
29
|
+
"removeDeps": ["@softeneers/storage"]
|
|
30
|
+
},
|
|
31
|
+
"payments": {
|
|
32
|
+
"removePaths": ["src/payments"],
|
|
33
|
+
"removeDeps": ["@softeneers/payments"]
|
|
15
34
|
}
|
|
16
35
|
}
|
|
17
36
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
|
|
3
|
+
import { sendEmail } from "@softeneers/email";
|
|
4
|
+
|
|
5
|
+
import { env } from "../env.js";
|
|
6
|
+
import { mailer } from "./mailer.js";
|
|
7
|
+
|
|
8
|
+
export const emailRouter = Router();
|
|
9
|
+
|
|
10
|
+
// Demo: send a welcome email. POST { "to": "a@b.com", "name": "Ada" }
|
|
11
|
+
emailRouter.post("/welcome", async (req, res) => {
|
|
12
|
+
const to = typeof req.body?.to === "string" ? req.body.to : "";
|
|
13
|
+
const name = typeof req.body?.name === "string" ? req.body.name : "there";
|
|
14
|
+
if (!to) {
|
|
15
|
+
res.status(400).json({ message: "to (email address) is required." });
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
await sendEmail(mailer, {
|
|
19
|
+
from: env.EMAIL_FROM,
|
|
20
|
+
to,
|
|
21
|
+
subject: "Welcome!",
|
|
22
|
+
html: `<h1>Welcome, ${name}!</h1><p>Thanks for joining.</p>`,
|
|
23
|
+
text: `Welcome, ${name}! Thanks for joining.`,
|
|
24
|
+
});
|
|
25
|
+
res.json({ sent: true });
|
|
26
|
+
});
|
|
@@ -19,5 +19,23 @@ export const env = createEnv({
|
|
|
19
19
|
AUTH_SECRET: z.string().min(16).default("dev-secret-change-me-to-a-long-random-string"),
|
|
20
20
|
AUTH_BASE_URL: z.string().default("http://localhost:4000"),
|
|
21
21
|
// #endif
|
|
22
|
+
// #if email
|
|
23
|
+
RESEND_API_KEY: z.string().default("re_set_me_in_dotenv"),
|
|
24
|
+
EMAIL_FROM: z.string().default("onboarding@resend.dev"),
|
|
25
|
+
// #endif
|
|
26
|
+
// #if storage
|
|
27
|
+
S3_ACCESS_KEY_ID: z.string().default(""),
|
|
28
|
+
S3_SECRET_ACCESS_KEY: z.string().default(""),
|
|
29
|
+
S3_BUCKET: z.string().default("uploads"),
|
|
30
|
+
S3_REGION: z.string().default("auto"),
|
|
31
|
+
S3_ENDPOINT: z.string().default(""),
|
|
32
|
+
// #endif
|
|
33
|
+
// #if payments
|
|
34
|
+
APP_URL: z.string().default("http://localhost:4000"),
|
|
35
|
+
STRIPE_SECRET_KEY: z.string().default("sk_test_set_me_in_dotenv"),
|
|
36
|
+
STRIPE_WEBHOOK_SECRET: z.string().default("whsec_set_me_in_dotenv"),
|
|
37
|
+
STRIPE_PRICE_ID: z.string().default("price_set_me_in_dotenv"),
|
|
38
|
+
STRIPE_SUBSCRIPTION_PRICE_ID: z.string().default("price_set_me_in_dotenv"),
|
|
39
|
+
// #endif
|
|
22
40
|
},
|
|
23
41
|
});
|
|
@@ -8,6 +8,16 @@ import { toNodeHandler } from "@softeneers/auth";
|
|
|
8
8
|
|
|
9
9
|
import { auth } from "./auth/auth.js";
|
|
10
10
|
// #endif
|
|
11
|
+
// #if email
|
|
12
|
+
import { emailRouter } from "./email/routes.js";
|
|
13
|
+
// #endif
|
|
14
|
+
// #if storage
|
|
15
|
+
import { storageRouter } from "./storage/routes.js";
|
|
16
|
+
// #endif
|
|
17
|
+
// #if payments
|
|
18
|
+
import { paymentsRouter } from "./payments/routes.js";
|
|
19
|
+
import { stripeWebhookHandler } from "./payments/webhook.js";
|
|
20
|
+
// #endif
|
|
11
21
|
|
|
12
22
|
const app = express();
|
|
13
23
|
|
|
@@ -15,6 +25,10 @@ const app = express();
|
|
|
15
25
|
// better-auth handles its own body parsing, so mount it before express.json().
|
|
16
26
|
app.all("/api/auth/*splat", toNodeHandler(auth));
|
|
17
27
|
// #endif
|
|
28
|
+
// #if payments
|
|
29
|
+
// Stripe webhook signature verification needs the raw body — mount before express.json().
|
|
30
|
+
app.post("/api/webhooks/stripe", express.raw({ type: "application/json" }), stripeWebhookHandler);
|
|
31
|
+
// #endif
|
|
18
32
|
|
|
19
33
|
app.use(cors({ origin: env.CORS_ORIGIN }));
|
|
20
34
|
app.use(express.json());
|
|
@@ -24,6 +38,15 @@ app.get("/health", (_req, res) => {
|
|
|
24
38
|
});
|
|
25
39
|
|
|
26
40
|
app.use("/api/cars", carRouter);
|
|
41
|
+
// #if email
|
|
42
|
+
app.use("/api/email", emailRouter);
|
|
43
|
+
// #endif
|
|
44
|
+
// #if storage
|
|
45
|
+
app.use("/api/files", storageRouter);
|
|
46
|
+
// #endif
|
|
47
|
+
// #if payments
|
|
48
|
+
app.use("/api/payments", paymentsRouter);
|
|
49
|
+
// #endif
|
|
27
50
|
|
|
28
51
|
app.use(
|
|
29
52
|
(error: unknown, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
|
|
3
|
+
import { createBillingPortalSession, createCheckoutSession } from "@softeneers/payments";
|
|
4
|
+
|
|
5
|
+
import { env } from "../env.js";
|
|
6
|
+
import { stripe } from "./stripe.js";
|
|
7
|
+
|
|
8
|
+
export const paymentsRouter = Router();
|
|
9
|
+
|
|
10
|
+
// One-time purchase → returns a Stripe Checkout URL to redirect the browser to.
|
|
11
|
+
paymentsRouter.post("/checkout", async (_req, res) => {
|
|
12
|
+
const session = await createCheckoutSession(stripe, {
|
|
13
|
+
mode: "payment",
|
|
14
|
+
priceId: env.STRIPE_PRICE_ID,
|
|
15
|
+
successUrl: `${env.APP_URL}/?paid=1`,
|
|
16
|
+
cancelUrl: `${env.APP_URL}/?canceled=1`,
|
|
17
|
+
});
|
|
18
|
+
res.json({ url: session.url });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Subscription checkout.
|
|
22
|
+
paymentsRouter.post("/subscribe", async (_req, res) => {
|
|
23
|
+
const session = await createCheckoutSession(stripe, {
|
|
24
|
+
mode: "subscription",
|
|
25
|
+
priceId: env.STRIPE_SUBSCRIPTION_PRICE_ID,
|
|
26
|
+
successUrl: `${env.APP_URL}/?subscribed=1`,
|
|
27
|
+
cancelUrl: `${env.APP_URL}/?canceled=1`,
|
|
28
|
+
});
|
|
29
|
+
res.json({ url: session.url });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Billing portal so a customer can manage their subscription. Pass ?customer=cus_…
|
|
33
|
+
paymentsRouter.post("/portal", async (req, res) => {
|
|
34
|
+
const customer = String(req.query.customer ?? "");
|
|
35
|
+
if (!customer) {
|
|
36
|
+
res.status(400).json({ message: "A Stripe customer id (?customer=cus_…) is required." });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const session = await createBillingPortalSession(stripe, {
|
|
40
|
+
customer,
|
|
41
|
+
returnUrl: `${env.APP_URL}/`,
|
|
42
|
+
});
|
|
43
|
+
res.json({ url: session.url });
|
|
44
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Request, Response } from "express";
|
|
2
|
+
|
|
3
|
+
import { constructWebhookEvent } from "@softeneers/payments";
|
|
4
|
+
|
|
5
|
+
import { env } from "../env.js";
|
|
6
|
+
import { stripe } from "./stripe.js";
|
|
7
|
+
|
|
8
|
+
// Stripe webhook. Mounted with express.raw() in index.ts because signature
|
|
9
|
+
// verification needs the raw request body. Handles both the one-time-payment
|
|
10
|
+
// and subscription event sets.
|
|
11
|
+
export function stripeWebhookHandler(req: Request, res: Response): void {
|
|
12
|
+
const signature = req.headers["stripe-signature"];
|
|
13
|
+
if (typeof signature !== "string") {
|
|
14
|
+
res.status(400).send("Missing stripe-signature header.");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let event;
|
|
19
|
+
try {
|
|
20
|
+
event = constructWebhookEvent(stripe, req.body, signature, env.STRIPE_WEBHOOK_SECRET);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error("Stripe webhook signature verification failed:", error);
|
|
23
|
+
res.status(400).send("Invalid signature.");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
switch (event.type) {
|
|
28
|
+
case "checkout.session.completed":
|
|
29
|
+
// TODO: fulfil the order / mark the user as paid.
|
|
30
|
+
console.log("✓ checkout.session.completed", event.data.object.id);
|
|
31
|
+
break;
|
|
32
|
+
case "customer.subscription.created":
|
|
33
|
+
case "customer.subscription.updated":
|
|
34
|
+
case "customer.subscription.deleted":
|
|
35
|
+
// TODO: sync the subscription state to your database.
|
|
36
|
+
console.log(`✓ ${event.type}`, event.data.object.id);
|
|
37
|
+
break;
|
|
38
|
+
default:
|
|
39
|
+
console.log("Unhandled Stripe event:", event.type);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
res.json({ received: true });
|
|
43
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Router, raw } from "express";
|
|
2
|
+
|
|
3
|
+
import { deleteFile, getSignedDownloadUrl, uploadFile } from "@softeneers/storage";
|
|
4
|
+
|
|
5
|
+
import { storage } from "./store.js";
|
|
6
|
+
|
|
7
|
+
export const storageRouter = Router();
|
|
8
|
+
|
|
9
|
+
// Upload raw bytes: PUT/POST the file body to /api/files/:key
|
|
10
|
+
storageRouter.post("/:key", raw({ type: "*/*", limit: "10mb" }), async (req, res) => {
|
|
11
|
+
await uploadFile(storage, {
|
|
12
|
+
key: req.params.key,
|
|
13
|
+
body: req.body as Buffer,
|
|
14
|
+
contentType: req.headers["content-type"],
|
|
15
|
+
});
|
|
16
|
+
res.status(201).json({ key: req.params.key });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Get a time-limited download URL for an object.
|
|
20
|
+
storageRouter.get("/:key/url", async (req, res) => {
|
|
21
|
+
res.json({ url: await getSignedDownloadUrl(storage, req.params.key) });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Delete an object.
|
|
25
|
+
storageRouter.delete("/:key", async (req, res) => {
|
|
26
|
+
await deleteFile(storage, req.params.key);
|
|
27
|
+
res.status(204).end();
|
|
28
|
+
});
|