belt 0.4.4 → 0.4.6
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +66 -0
- data/README.md +30 -0
- data/SKILL.md +144 -0
- data/lib/belt/cli/db_copy_command.rb +150 -0
- data/lib/belt/cli/db_seed_command.rb +178 -0
- data/lib/belt/cli/deploy_command.rb +20 -1
- data/lib/belt/cli/dynamo_copier.rb +185 -73
- data/lib/belt/cli/explain_command.rb +10 -1
- data/lib/belt/cli/frontend_deploy_command.rb +58 -1
- data/lib/belt/cli/new_command.rb +1 -0
- data/lib/belt/cli.rb +10 -0
- data/lib/belt/docs/data_seeding.md +129 -0
- data/lib/belt/version.rb +1 -1
- data/lib/templates/new_app/config/seeds.rb.erb +24 -0
- data/references/cli.md +76 -0
- data/references/controllers.md +73 -0
- data/references/deploy-and-ops.md +77 -0
- data/references/models-and-auth.md +71 -0
- data/references/plugins.md +64 -0
- data/references/routing.md +78 -0
- metadata +12 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Data Seeding
|
|
2
|
+
|
|
3
|
+
Belt provides two ways to get data into an environment without hand-crafting
|
|
4
|
+
rows: copying data from another environment, and Rails-style seed files.
|
|
5
|
+
|
|
6
|
+
## `belt db:copy` — copy data between environments
|
|
7
|
+
|
|
8
|
+
Copies DynamoDB table contents from one environment into another, matching
|
|
9
|
+
tables by name after stripping each environment's `<app>-<env>-` prefix.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
belt db:copy prod dev # copy prod data into dev
|
|
13
|
+
belt db:copy prod dev --force # overwrite dev tables even if non-empty
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
By default, destination tables that already contain data are skipped — safe
|
|
17
|
+
to re-run against a live environment. `--force` overwrites them instead.
|
|
18
|
+
|
|
19
|
+
### Cognito identity re-anchoring
|
|
20
|
+
|
|
21
|
+
Cognito identities are **per-environment**: each environment has its own user
|
|
22
|
+
pool, so the same person has a *different* `sub` in every environment. Any row
|
|
23
|
+
that references a user by their `sub` (e.g. a membership's `cognito_sub`) has a
|
|
24
|
+
reference that's meaningless in another environment — copy it verbatim and the
|
|
25
|
+
row points at a user who doesn't exist in the destination pool, so it silently
|
|
26
|
+
disappears (a copied project you can't see, a member who isn't there).
|
|
27
|
+
|
|
28
|
+
`belt db:copy` handles this automatically:
|
|
29
|
+
|
|
30
|
+
- The destination's own `users` table is **left untouched** — the destination
|
|
31
|
+
pool is authoritative for who its users are and what `sub` each one has.
|
|
32
|
+
- For every other table, any row carrying both an `email` and a `cognito_sub`
|
|
33
|
+
has its `cognito_sub` **re-anchored** to the destination user with the same
|
|
34
|
+
email.
|
|
35
|
+
- A row whose email has no destination user yet has its stale `cognito_sub`
|
|
36
|
+
**cleared**, so it reads as unclaimed (e.g. a pending invitation Belt binds
|
|
37
|
+
on that person's first login) rather than dangling.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
belt db:copy prod dev # re-anchors identities (default)
|
|
41
|
+
belt db:copy prod dev --no-remap-identity # copy cognito_sub refs verbatim
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This relies on Belt's `cognito_authenticatable` convention: the users table is
|
|
45
|
+
`<app>-<env>-users`, its primary key is `id` (the Cognito `sub`), it carries an
|
|
46
|
+
`email`, and `cognito_sub` is the foreign-key attribute referencing it. The
|
|
47
|
+
same re-anchoring runs in the nested (PR-preview) environment deploy hook.
|
|
48
|
+
|
|
49
|
+
### Cross-account copies
|
|
50
|
+
|
|
51
|
+
Source and destination environments often live in different AWS accounts
|
|
52
|
+
(e.g. prod vs. dev). `belt db:copy` resolves the AWS profile for each side
|
|
53
|
+
independently from `infrastructure/<env>/belt.rb` (`config.aws_profile`):
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
# infrastructure/prod/belt.rb
|
|
57
|
+
Belt.configure do |config|
|
|
58
|
+
config.aws_profile = "prod-readonly"
|
|
59
|
+
end
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Override either side explicitly if you don't want to rely on `belt.rb`:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
belt db:copy prod dev --from-profile prod-readonly --to-profile dev
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### How it works
|
|
69
|
+
|
|
70
|
+
1. Lists tables under each environment's prefix (`<app>-<env>-`) using the
|
|
71
|
+
AWS CLI (`aws dynamodb list-tables`)
|
|
72
|
+
2. Pairs up tables by matching suffix (e.g. `myapp-prod-posts` ↔ `myapp-dev-posts`)
|
|
73
|
+
3. Scans the source table and `batch-write-item`s into the destination,
|
|
74
|
+
re-anchoring Cognito-sub foreign keys to the destination's users by email
|
|
75
|
+
(unless `--no-remap-identity`; see above)
|
|
76
|
+
4. Skips (or overwrites, with `--force`) destination tables that already
|
|
77
|
+
have items
|
|
78
|
+
|
|
79
|
+
This is the same mechanism used by nested (PR-preview) environment deploys
|
|
80
|
+
to seed a preview environment's tables from its parent — `belt db:copy` just
|
|
81
|
+
exposes it as a standalone command for any two environments.
|
|
82
|
+
|
|
83
|
+
## `belt db:seed` — Rails-style seed file
|
|
84
|
+
|
|
85
|
+
Mirrors `rails db:seed`. Loads `config/seeds.rb` in the same booted context
|
|
86
|
+
`belt console` uses — your models (ActiveItem) are available, targeting the
|
|
87
|
+
resolved environment's tables.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
belt db:seed # seeds dev, or $BELT_ENV if set
|
|
91
|
+
belt db:seed dev01 # explicit environment
|
|
92
|
+
belt db:seed prod # prompts for confirmation, like belt console prod
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`config/seeds.rb` is a plain Ruby file:
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
# frozen_string_literal: true
|
|
99
|
+
|
|
100
|
+
post = Post.create!(title: "Hello, world", body: "Seeded post")
|
|
101
|
+
puts "Created post: #{post.id}"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Safety
|
|
105
|
+
|
|
106
|
+
`belt db:seed` refuses to run if the target environment's tables already
|
|
107
|
+
have data, to avoid clobbering a live environment (or accidentally reseeding
|
|
108
|
+
one that's already loaded). Pass `--force` to seed anyway:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
belt db:seed dev01 --force
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Because of this guard, seeds are typically run once against a fresh
|
|
115
|
+
environment. If you want `seeds.rb` to be safely re-runnable regardless,
|
|
116
|
+
write it idempotently (`find_or_create_by`-style) — `belt db:seed --force`
|
|
117
|
+
does not enforce idempotency for you.
|
|
118
|
+
|
|
119
|
+
### Scaffolding
|
|
120
|
+
|
|
121
|
+
`belt new` generates a starter `config/seeds.rb` with usage notes and an
|
|
122
|
+
example. Existing apps can add the file manually — it's just a plain Ruby
|
|
123
|
+
file, no generator required.
|
|
124
|
+
|
|
125
|
+
## See Also
|
|
126
|
+
|
|
127
|
+
- `belt explain backups` — recovery-point snapshots, not data seeding
|
|
128
|
+
- `belt explain console` — the app-booting mechanism `db:seed` reuses
|
|
129
|
+
- `belt explain deployment` — nested environments and the parent → child copy hook
|
data/lib/belt/version.rb
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Seed data for <%= @app_name %>. Run with:
|
|
4
|
+
#
|
|
5
|
+
# belt db:seed # seeds dev (or BELT_ENV)
|
|
6
|
+
# belt db:seed prod # explicit environment (prompts for confirmation)
|
|
7
|
+
# BELT_ENV=dev01 belt db:seed
|
|
8
|
+
#
|
|
9
|
+
# This file is loaded in the same booted context as `belt console` — your
|
|
10
|
+
# models (ActiveItem) are available, and they target the resolved
|
|
11
|
+
# environment's DynamoDB tables (<%= @app_name %>-<env>-*).
|
|
12
|
+
#
|
|
13
|
+
# `belt db:seed` refuses to run against an environment that already has
|
|
14
|
+
# data, to avoid clobbering something real. Pass --force to seed anyway.
|
|
15
|
+
# Keep this file idempotent if you expect to re-run it (e.g. find_or_create
|
|
16
|
+
# rather than create).
|
|
17
|
+
#
|
|
18
|
+
# Example:
|
|
19
|
+
#
|
|
20
|
+
# post = Post.create!(
|
|
21
|
+
# title: "Hello, world",
|
|
22
|
+
# body: "This post was created by config/seeds.rb"
|
|
23
|
+
# )
|
|
24
|
+
# puts "Created post: #{post.id}"
|
data/references/cli.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Belt CLI reference
|
|
2
|
+
|
|
3
|
+
Run `belt --help` for the live list, or `belt <command> --help` for a specific
|
|
4
|
+
command. `belt explain <topic>` gives conceptual docs. `BELT_ENV` sets the
|
|
5
|
+
default environment so you can omit the `<env>` argument.
|
|
6
|
+
|
|
7
|
+
## Commands
|
|
8
|
+
|
|
9
|
+
| Command | What it does |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `belt new <app> [--frontend react]` | Create a new Belt app. `-v` lists every created file. |
|
|
12
|
+
| `belt generate <thing> <name>` (alias `g`) | Generate `scaffold`, `model`, `controller`, `frontend`, `views`, `environment`, `dns`, `auth`, or a plugin generator. |
|
|
13
|
+
| `belt destroy <thing> <name>` (alias `d`) | Remove what `generate` created. |
|
|
14
|
+
| `belt routes [-g PATTERN] [-f json] [--namespace N]` | Show/inspect routes; generate Ruby route constants for the runtime router. |
|
|
15
|
+
| `belt contracts [-g PATTERN] [-f json]` | Show API request/response contracts. |
|
|
16
|
+
| `belt lambda-config [-e ENV] [-f json\|terraform]` | Show merged Lambda configuration. |
|
|
17
|
+
| `belt console [env]` (alias `c`) | Interactive IRB with the app booted. `--run "expr"` for runner mode. |
|
|
18
|
+
| `belt logs [lambda] [-f] [-s 5m] [-e env]` | Tail Lambda logs. |
|
|
19
|
+
| `belt tasks [-g PATTERN] [-a]` (alias `-T`) | List rake tasks. Any rake task can be run directly: `belt lambda:build_layer`. |
|
|
20
|
+
| `belt setup <state\|tables <env>\|frontend>` | Create S3 state bucket / generate DynamoDB tables / frontend infra. |
|
|
21
|
+
| `belt doctor` | Check system deps + AWS config. |
|
|
22
|
+
| `belt plugin new <name>` | Scaffold a Belt plugin gem. |
|
|
23
|
+
| `belt explain <topic>` | Explain a concept (see topic list below). |
|
|
24
|
+
| `belt deploy [env] [--auto] [--skip-backup] [--backup-only]` | Deploy to AWS (init → plan → apply, runs backups first if configured). |
|
|
25
|
+
| `belt deploy frontend <env> [--frontend NAME]` | Build + deploy frontend(s). |
|
|
26
|
+
| `belt dns <deploy\|add <env>\|show>` | Manage the root DNS zone. |
|
|
27
|
+
| `belt frontend <env <env>\|list>` | Write `<frontend>/.env` from TF outputs, or list frontends. |
|
|
28
|
+
| `belt server [--frontend NAME]` (alias `s`) | Start local dev server. |
|
|
29
|
+
| `belt db:copy <from> <to> [--force]` | Copy DynamoDB data between environments. |
|
|
30
|
+
| `belt db:seed [env] [--force]` | Run `config/seeds.rb` against an environment. |
|
|
31
|
+
| `belt version` | Show Belt version. |
|
|
32
|
+
|
|
33
|
+
### Terraform shorthand
|
|
34
|
+
|
|
35
|
+
`belt <action> [env]` maps to Terraform: `init`, `plan`, `apply`, `destroy`,
|
|
36
|
+
`output`. Example: `belt apply wups`, `belt output prod`.
|
|
37
|
+
|
|
38
|
+
> ⚠ `belt destroy` is ambiguous: `belt destroy <env>` runs terraform destroy,
|
|
39
|
+
> while `belt destroy scaffold post` removes generated code. Belt disambiguates
|
|
40
|
+
> by argument shape.
|
|
41
|
+
|
|
42
|
+
## `belt explain` topics
|
|
43
|
+
|
|
44
|
+
`routing`, `controllers`, `models`, `deployment`, `generators`,
|
|
45
|
+
`lambda_handler`, `observability`, `console`, `backups`, `data_seeding`,
|
|
46
|
+
`plugins`, `structure`, `frontend`, `authentication`.
|
|
47
|
+
|
|
48
|
+
## Standalone vs project commands
|
|
49
|
+
|
|
50
|
+
These run anywhere (no Belt project needed): `new`, `version`, `doctor`,
|
|
51
|
+
`explain`. All others chdir to the detected project root first.
|
|
52
|
+
|
|
53
|
+
## Environment variables
|
|
54
|
+
|
|
55
|
+
| Variable | Purpose |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `BELT_ENV` | Default environment for env-scoped commands |
|
|
58
|
+
| `ENVIRONMENT` | Verbose error responses (`dev*`, `local`, `test`) |
|
|
59
|
+
| `BELT_METRICS_NAMESPACE` | CloudWatch metrics namespace (default `Belt`) |
|
|
60
|
+
| `ACTION` | Service name for logging (falls back to function name) |
|
|
61
|
+
| `ERROR_NOTIFICATION_TOPIC_ARN` | SNS topic for error alerts |
|
|
62
|
+
| `CORS_ALLOWED_ORIGINS` | Comma-separated origins (overrides domain vars) |
|
|
63
|
+
| `CUSTOMER_APP_DOMAIN` / `OPS_APP_DOMAIN` | CORS domains |
|
|
64
|
+
|
|
65
|
+
## Common flows
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
belt new blog --frontend react
|
|
69
|
+
belt generate scaffold post title:string content:text
|
|
70
|
+
belt routes
|
|
71
|
+
belt deploy dev
|
|
72
|
+
belt deploy prod --auto
|
|
73
|
+
belt console prod --run "Post.count"
|
|
74
|
+
belt logs api -f -e prod
|
|
75
|
+
belt db:copy prod dev
|
|
76
|
+
```
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# BeltController
|
|
2
|
+
|
|
3
|
+
`BeltController::Base` gives Rails-like callbacks, strong params, response
|
|
4
|
+
helpers, and error handling. Run `belt explain controllers` for canonical docs.
|
|
5
|
+
|
|
6
|
+
## Implicit responses
|
|
7
|
+
|
|
8
|
+
Instance variables assigned in an action become the JSON body by default:
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
def index
|
|
12
|
+
@posts = Post.all # → { "posts": [ ... ] }
|
|
13
|
+
end
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Explicit helpers always override implicit assigns.
|
|
17
|
+
|
|
18
|
+
## Callbacks
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
before_action :authenticate_user!
|
|
22
|
+
before_action :require_admin!, except: [:health]
|
|
23
|
+
skip_before_action :authenticate_user!, only: [:health]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Strong parameters
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
params.require(:user).permit(:name, :email, address: [:street, :city])
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Response helpers
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
success_response({ id: "123" }) # 200 JSON + CORS
|
|
36
|
+
success_response({ id: "123" }, :created) # 201 (symbol or int)
|
|
37
|
+
error_response("Not found", :not_found) # 404 JSON error
|
|
38
|
+
error_response("Nope", :unprocessable_entity) # 422
|
|
39
|
+
html_response("<h1>Hi</h1>") # 200 HTML + CORS
|
|
40
|
+
head :no_content # 204 empty
|
|
41
|
+
response_status :created # 201 + implicit assigns
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Error handling
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
rescue_from MyError, with: :handle_it
|
|
48
|
+
|
|
49
|
+
def handle_it(exception, _context = {})
|
|
50
|
+
error_response(exception.message, 422)
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Default format (JSON vs HTML)
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
# App-wide (lambda/config/environment.rb)
|
|
58
|
+
Belt.configure { |c| c.default_format = :json } # default
|
|
59
|
+
|
|
60
|
+
# Per-controller
|
|
61
|
+
class PagesController < ApplicationController
|
|
62
|
+
self.default_format = :html # implicitly renders views/<controller>/<action>.html.erb
|
|
63
|
+
end
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- `:json` (default): assigns → `success_response({ ... })`.
|
|
67
|
+
- `:html`: Belt implicitly renders the ERB template. Missing template raises
|
|
68
|
+
`Belt::TemplateNotFound` (no silent JSON fallback).
|
|
69
|
+
|
|
70
|
+
## Controller discovery
|
|
71
|
+
|
|
72
|
+
No registration needed. Belt looks in the app namespace module first, then
|
|
73
|
+
`Belt.all_controller_paths`.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Deploy, environments, backups, seeding & observability
|
|
2
|
+
|
|
3
|
+
Run `belt explain deployment`, `belt explain backups`, `belt explain
|
|
4
|
+
data_seeding`, and `belt explain observability` for canonical docs.
|
|
5
|
+
|
|
6
|
+
## Deploy lifecycle
|
|
7
|
+
|
|
8
|
+
`belt deploy [env]` runs pre-deploy backups (if configured) → terraform init →
|
|
9
|
+
plan → apply. The **conveyor-belt** Terraform provider packages Ruby into
|
|
10
|
+
Lambdas, creates API Gateway routes from the routing DSL, generates IAM for
|
|
11
|
+
DynamoDB access, and sets up CloudWatch log groups.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
belt deploy dev
|
|
15
|
+
belt deploy prod --auto # skip confirmation
|
|
16
|
+
belt deploy prod --skip-backup # CI re-runs
|
|
17
|
+
belt deploy prod --backup-only # recovery point, no deploy
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Provider config (Terraform):
|
|
21
|
+
|
|
22
|
+
```hcl
|
|
23
|
+
terraform {
|
|
24
|
+
required_providers {
|
|
25
|
+
conveyor-belt = { source = "stowzilla/conveyor-belt", version = "~> 0.0.1" }
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Environments
|
|
31
|
+
|
|
32
|
+
Each env has `infrastructure/<env>/` (main.tf, backend.tf, variables.tf,
|
|
33
|
+
terraform.tfvars, outputs.tf, belt.rb). Create with `belt generate environment
|
|
34
|
+
<name> [parent]`. Terraform shorthand: `belt init|plan|apply|destroy|output <env>`.
|
|
35
|
+
Set `BELT_ENV` to omit the env arg.
|
|
36
|
+
|
|
37
|
+
## Backups (pre-deploy, config-driven)
|
|
38
|
+
|
|
39
|
+
`infrastructure/<env>/belt.rb`:
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
Belt.configure do |config|
|
|
43
|
+
config.backups do
|
|
44
|
+
dynamodb :all # PITR check + on-demand snapshot per table
|
|
45
|
+
cognito :users, :pool_config # export to backup bucket
|
|
46
|
+
s3 :legal_documents # sync to backup bucket
|
|
47
|
+
retention snapshots: 90, cognito: 10, s3: 10
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Simple mode: `config.backups = true` (DynamoDB, all tables, 90-day retention).
|
|
53
|
+
Omit the block entirely for lightweight dev envs. Belt auto-creates
|
|
54
|
+
`<app>-backups-<env>` (versioned, public access blocked) on first run. Table
|
|
55
|
+
names come from `terraform output`, so the first-ever deploy skips backups.
|
|
56
|
+
|
|
57
|
+
## Data seeding
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
belt db:copy prod dev [--force] # copy DynamoDB between envs (matches by stripped prefix)
|
|
61
|
+
belt db:seed [env] [--force] # run config/seeds.rb in the booted console context
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`db:copy` skips non-empty destination tables by default. `db:seed` refuses to
|
|
65
|
+
run against an env that already has data unless `--force`.
|
|
66
|
+
|
|
67
|
+
## Observability
|
|
68
|
+
|
|
69
|
+
`Belt::LambdaHandler` wires these global facades automatically:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
Belt::Observability::Logger.info("Something happened", user_id: "123")
|
|
73
|
+
Belt::Observability::Metrics.track_event("OrderCreated", model: "Order")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Backed by `lambda_loadout` (structured logging + CloudWatch EMF metrics + error
|
|
77
|
+
alerting via `ERROR_NOTIFICATION_TOPIC_ARN`).
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Models (ActiveItem) & Cognito authentication
|
|
2
|
+
|
|
3
|
+
Run `belt explain models` and `belt explain authentication` for canonical docs.
|
|
4
|
+
|
|
5
|
+
## ActiveItem (DynamoDB ORM)
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
require "activeitem"
|
|
9
|
+
|
|
10
|
+
class Post < ActiveItem::Base
|
|
11
|
+
self.primary_key = :id
|
|
12
|
+
attr_accessor :id, :user_id, :title, :body, :created_at
|
|
13
|
+
|
|
14
|
+
validates :title, presence: true
|
|
15
|
+
before_create { self.id ||= SecureRandom.uuid }
|
|
16
|
+
end
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Supports queries, validations, associations, and transactions. Query a GSI:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
Post.where(user_id: current_user.id, index: "UserIndex")
|
|
23
|
+
Post.find(id)
|
|
24
|
+
Post.create!(attrs)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Table schema is declared in `infrastructure/schema.tf.rb`:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
Belt.application.schema.define do
|
|
31
|
+
model :post do
|
|
32
|
+
partition_key :id, :string
|
|
33
|
+
global_secondary_index :UserIndex, partition_key: :user_id
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
DynamoDB tables generated by Belt default to PITR enabled and (in prod)
|
|
39
|
+
deletion protection enabled.
|
|
40
|
+
|
|
41
|
+
## Authentication — Cognito owns auth, Belt owns the record
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
class User < ApplicationRecord
|
|
45
|
+
cognito_authenticatable
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
One line supplies: Cognito `sub` as primary key, identity attributes (`email`,
|
|
50
|
+
`name`, `role`, `email_verified`, `last_seen_on`), an `EmailIndex` GSI,
|
|
51
|
+
just-in-time provisioning from a token, and `#admin?` for platform staff.
|
|
52
|
+
|
|
53
|
+
Controllers get helpers for free — no `include`, no config:
|
|
54
|
+
|
|
55
|
+
| Helper | Meaning |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `current_user` | User record or nil (memoized per request) |
|
|
58
|
+
| `user_signed_in?` | Is there a Cognito identity on this request? |
|
|
59
|
+
| `authenticate_user!` | `before_action` guard → 401 |
|
|
60
|
+
| `cognito_admin?` | Does the token carry a staff Cognito group? |
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
class ProfilesController < ApplicationController
|
|
64
|
+
before_action :authenticate_user!
|
|
65
|
+
def show = @profile = current_user
|
|
66
|
+
end
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`belt generate auth` creates the user pool **and** scaffolds the model + table.
|
|
70
|
+
See `belt explain authentication` for the `after_cognito_sync` hook, platform
|
|
71
|
+
staff handling, and both token shapes. Upgrading an existing app? See `UPGRADING.md`.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Authoring Belt plugins
|
|
2
|
+
|
|
3
|
+
Belt stays lean; optional capabilities ship as **separate gems** that plug into
|
|
4
|
+
the CLI and runtime. Run `belt explain plugins` for canonical docs. Reference
|
|
5
|
+
implementations: `belt-messaging`, `belt-pay`.
|
|
6
|
+
|
|
7
|
+
## Discovery contract (GeneratorRegistry)
|
|
8
|
+
|
|
9
|
+
No central registry, no initializer. Belt discovers a generator when:
|
|
10
|
+
|
|
11
|
+
1. The gem is in the app's `Gemfile` and bundled.
|
|
12
|
+
2. It ships `lib/belt/generators/<name>_generator.rb`.
|
|
13
|
+
3. The class is `Belt::Generators::<Name>Generator`.
|
|
14
|
+
4. It implements `.run(args)` (required); optionally `.destroy(args)` and `.description`.
|
|
15
|
+
|
|
16
|
+
After `bundle install`, `belt generate <name>` and `belt destroy <name>` just work.
|
|
17
|
+
|
|
18
|
+
## Scaffold a plugin
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
belt plugin new notifications # → ./belt-notifications/
|
|
22
|
+
belt plugin new pay --path ~/Code --summary "Stripe payments for Belt"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Point an app at a local plugin while developing:
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
# app Gemfile
|
|
29
|
+
gem "belt-notifications", path: "../belt-notifications"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`belt deploy` vendors `path:` gems into `vendor/cache` so conveyor-belt can
|
|
33
|
+
package them.
|
|
34
|
+
|
|
35
|
+
## Canonical layout
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
belt-messaging/
|
|
39
|
+
├── belt-messaging.gemspec
|
|
40
|
+
├── lib/
|
|
41
|
+
│ ├── belt-messaging.rb # require entrypoint
|
|
42
|
+
│ └── belt/
|
|
43
|
+
│ ├── messaging.rb # Belt::Messaging API
|
|
44
|
+
│ ├── messaging/{configuration,version}.rb
|
|
45
|
+
│ ├── messaging/controllers/ # default controllers (optional)
|
|
46
|
+
│ ├── messaging/templates/ # ERB for the generator
|
|
47
|
+
│ └── generators/messaging_generator.rb # ← auto-discovered
|
|
48
|
+
└── spec/
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Runtime code stays in the gem.** Generators copy only what the host app must
|
|
52
|
+
own — Terraform modules, Lambda entrypoints, optional controller overrides.
|
|
53
|
+
Prefer gem defaults + `belt g <plugin> --controllers` over dumping everything
|
|
54
|
+
into the app.
|
|
55
|
+
|
|
56
|
+
## Generator checklist
|
|
57
|
+
|
|
58
|
+
1. Terraform module → `infrastructure/modules/<name>/`
|
|
59
|
+
2. Lambda config → `config/lambda/<name>.yml`
|
|
60
|
+
3. Lambda entrypoint → `lambda/<name>.rb` via `Belt::LambdaHandler`
|
|
61
|
+
4. Routes/schema injection when needed
|
|
62
|
+
5. Optional `--controllers` for app-local overrides
|
|
63
|
+
6. Matching `destroy` path
|
|
64
|
+
7. `.description` + `--help`
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Belt routing DSL
|
|
2
|
+
|
|
3
|
+
Routes live in `infrastructure/routes.tf.rb` and are read both by the
|
|
4
|
+
**conveyor-belt** Terraform provider (for infra) and by `belt routes` (which
|
|
5
|
+
generates the runtime route constants at `lambda/lib/routes/<namespace>_routes.rb`).
|
|
6
|
+
|
|
7
|
+
Run `belt explain routing` for the canonical docs.
|
|
8
|
+
|
|
9
|
+
## Four keywords
|
|
10
|
+
|
|
11
|
+
| Keyword | Purpose | Changes which Lambda? |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `gateway` | API Gateway + default Lambda | **Yes** — sets default for routes inside |
|
|
14
|
+
| `function` | Route to a different Lambda | **Yes** — overrides gateway default |
|
|
15
|
+
| `namespace` | Path prefix + controller module | No — code organization only |
|
|
16
|
+
| `scope` | Path/module/auth grouping | No — grouping + shared options |
|
|
17
|
+
|
|
18
|
+
**Critical:** `namespace` and `scope` are purely organizational. Only `gateway`
|
|
19
|
+
and `function` determine the serving Lambda.
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
Belt.application.routes.draw do
|
|
23
|
+
gateway :api, auth: :cognito do
|
|
24
|
+
resources :posts # lambda: api, /posts, posts controller
|
|
25
|
+
|
|
26
|
+
namespace :admin do
|
|
27
|
+
resources :users # /admin/users, admin/users controller
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
function :worker do
|
|
31
|
+
resources :jobs # lambda: worker, /jobs, jobs controller
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
scope path: 'v2', module: 'legacy' do
|
|
35
|
+
resources :widgets # /v2/widgets, legacy/widgets controller
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Nested resources
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
resources :projects do
|
|
45
|
+
resource :billing, only: [:show], tables: [:memberships] # singular, no :id
|
|
46
|
+
|
|
47
|
+
resources :webhooks do
|
|
48
|
+
member { post :test } # POST /projects/:project_id/webhooks/:webhook_id/test
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
resources :surfaces do
|
|
52
|
+
collection { get :teams } # GET /projects/:project_id/surfaces/teams
|
|
53
|
+
member { put :assign } # PUT /projects/:project_id/surfaces/:surface_id/assign
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
scope path: 'billing', controller: :billing, tables: [:memberships] do
|
|
57
|
+
get '/', action: :show
|
|
58
|
+
post :checkout
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- **Action inference:** `post :checkout` uses `checkout` as both path segment and action.
|
|
64
|
+
- **Controller inheritance:** `member`/`collection` inherit the parent resource's controller.
|
|
65
|
+
- **`tables:`** declares DynamoDB access for IAM generation.
|
|
66
|
+
|
|
67
|
+
## Request/response model inference (for `belt routes` / contracts)
|
|
68
|
+
|
|
69
|
+
Resolution order (highest first):
|
|
70
|
+
|
|
71
|
+
1. **Explicit per-route:** `put "/items/:id", request_model: :update_item`
|
|
72
|
+
2. **Hash per-action:** `resources :items, request_model: { create: :create_item }`
|
|
73
|
+
3. **Convention cascade** (POST/PUT/PATCH only):
|
|
74
|
+
- `:<verb>_<gateway>_<singular>` → e.g. `:create_customer_item`
|
|
75
|
+
- `:<verb>_<singular>` → e.g. `:create_item`
|
|
76
|
+
|
|
77
|
+
**Response model:** singular of the resource name → `resources :items` looks for
|
|
78
|
+
`model :item` in `contracts.rb`. Applies to all verbs. No match = no model documented.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: belt
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stowzilla
|
|
@@ -97,6 +97,7 @@ files:
|
|
|
97
97
|
- CHANGELOG.md
|
|
98
98
|
- LICENSE.txt
|
|
99
99
|
- README.md
|
|
100
|
+
- SKILL.md
|
|
100
101
|
- exe/belt
|
|
101
102
|
- lib/belt.rb
|
|
102
103
|
- lib/belt/action_router.rb
|
|
@@ -119,6 +120,8 @@ files:
|
|
|
119
120
|
- lib/belt/cli/cognito_sharer.rb
|
|
120
121
|
- lib/belt/cli/console_command.rb
|
|
121
122
|
- lib/belt/cli/contracts_command.rb
|
|
123
|
+
- lib/belt/cli/db_copy_command.rb
|
|
124
|
+
- lib/belt/cli/db_seed_command.rb
|
|
122
125
|
- lib/belt/cli/deploy_command.rb
|
|
123
126
|
- lib/belt/cli/destroy_command.rb
|
|
124
127
|
- lib/belt/cli/dns_command.rb
|
|
@@ -160,6 +163,7 @@ files:
|
|
|
160
163
|
- lib/belt/docs/backups.md
|
|
161
164
|
- lib/belt/docs/console.md
|
|
162
165
|
- lib/belt/docs/controllers.md
|
|
166
|
+
- lib/belt/docs/data_seeding.md
|
|
163
167
|
- lib/belt/docs/deployment.md
|
|
164
168
|
- lib/belt/docs/frontend.md
|
|
165
169
|
- lib/belt/docs/generators.md
|
|
@@ -231,6 +235,7 @@ files:
|
|
|
231
235
|
- lib/templates/new_app/config/contracts.rb.erb
|
|
232
236
|
- lib/templates/new_app/config/lambda/api.yml.erb
|
|
233
237
|
- lib/templates/new_app/config/routes.rb.erb
|
|
238
|
+
- lib/templates/new_app/config/seeds.rb.erb
|
|
234
239
|
- lib/templates/new_app/gitignore.erb
|
|
235
240
|
- lib/templates/new_app/lambda/api.rb.erb
|
|
236
241
|
- lib/templates/new_app/lambda/config/environment.rb.erb
|
|
@@ -258,6 +263,12 @@ files:
|
|
|
258
263
|
- lib/templates/views/Index.jsx.erb
|
|
259
264
|
- lib/templates/views/New.jsx.erb
|
|
260
265
|
- lib/templates/views/Show.jsx.erb
|
|
266
|
+
- references/cli.md
|
|
267
|
+
- references/controllers.md
|
|
268
|
+
- references/deploy-and-ops.md
|
|
269
|
+
- references/models-and-auth.md
|
|
270
|
+
- references/plugins.md
|
|
271
|
+
- references/routing.md
|
|
261
272
|
homepage: https://github.com/stowzilla/belt
|
|
262
273
|
licenses:
|
|
263
274
|
- MIT
|