gemstack-cli 0.2.5 → 0.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -25
- data/README.md +6 -17
- data/exe/gemstack +8 -1
- metadata +6 -159
- data/lib/gemstack/cli/add_generator.rb +0 -239
- data/lib/gemstack/cli/app_generator.rb +0 -142
- data/lib/gemstack/cli/commands/db.rb +0 -135
- data/lib/gemstack/cli/commands/jobs.rb +0 -96
- data/lib/gemstack/cli/controller_generator.rb +0 -73
- data/lib/gemstack/cli/deploy_generator.rb +0 -102
- data/lib/gemstack/cli/doctor.rb +0 -308
- data/lib/gemstack/cli/generator.rb +0 -144
- data/lib/gemstack/cli/job_generator.rb +0 -48
- data/lib/gemstack/cli/migration_generator.rb +0 -52
- data/lib/gemstack/cli/policy_generator.rb +0 -35
- data/lib/gemstack/cli/project.rb +0 -53
- data/lib/gemstack/cli/resource_generator.rb +0 -170
- data/lib/gemstack/cli/resource_spec.rb +0 -95
- data/lib/gemstack/cli.rb +0 -304
- data/templates/app/Gemfile.tt +0 -44
- data/templates/app/README.md.tt +0 -29
- data/templates/app/app/controllers/application_controller.rb +0 -6
- data/templates/app/app/jobs/application_job.rb +0 -9
- data/templates/app/app/mailers/application_mailer.rb +0 -8
- data/templates/app/app/mailers/templates/dot_keep +0 -0
- data/templates/app/app/models/application_model.rb +0 -19
- data/templates/app/app/serializers/application_serializer.rb +0 -6
- data/templates/app/bin/gemstack +0 -7
- data/templates/app/config/app.rb.tt +0 -54
- data/templates/app/config/database.yml.tt +0 -61
- data/templates/app/config/environments/development.rb.tt +0 -35
- data/templates/app/config/environments/production.rb.tt +0 -40
- data/templates/app/config/environments/test.rb.tt +0 -21
- data/templates/app/config/puma.rb +0 -21
- data/templates/app/config/routes.rb +0 -10
- data/templates/app/config.ru +0 -6
- data/templates/app/db/migrations/dot_keep +0 -0
- data/templates/app/db/seeds.rb +0 -5
- data/templates/app/dot_env.example.tt +0 -19
- data/templates/app/dot_gitignore +0 -16
- data/templates/app/dot_node-version.tt +0 -1
- data/templates/app/dot_nvmrc.tt +0 -1
- data/templates/app/dot_ruby-version.tt +0 -1
- data/templates/app/dot_tool-versions.tt +0 -4
- data/templates/app/test/health_test.rb +0 -18
- data/templates/app/test/test_helper.rb.tt +0 -25
- data/templates/auth/app/controllers/api_tokens_controller.rb +0 -35
- data/templates/auth/app/controllers/email_verifications_controller.rb +0 -31
- data/templates/auth/app/controllers/password_resets_controller.rb +0 -40
- data/templates/auth/app/controllers/registrations_controller.rb +0 -19
- data/templates/auth/app/controllers/sessions_controller.rb +0 -29
- data/templates/auth/app/mailers/auth_mailer.rb +0 -20
- data/templates/auth/app/mailers/templates/auth_mailer/email_verification.html.erb +0 -2
- data/templates/auth/app/mailers/templates/auth_mailer/email_verification.text.erb +0 -2
- data/templates/auth/app/mailers/templates/auth_mailer/password_reset.html.erb +0 -3
- data/templates/auth/app/mailers/templates/auth_mailer/password_reset.text.erb +0 -6
- data/templates/auth/app/models/auth_token.rb +0 -15
- data/templates/auth/app/models/user.rb +0 -12
- data/templates/auth/app/policies/application_policy.rb +0 -6
- data/templates/auth/app/serializers/api_token_serializer.rb +0 -6
- data/templates/auth/app/serializers/new_api_token_serializer.rb +0 -9
- data/templates/auth/app/serializers/user_serializer.rb +0 -6
- data/templates/auth/db/migrations/%timestamp%_create_auth_tables.rb +0 -41
- data/templates/auth/frontend/app/account/page.tsx +0 -116
- data/templates/auth/frontend/app/forgot-password/page.tsx +0 -48
- data/templates/auth/frontend/app/login/page.tsx +0 -26
- data/templates/auth/frontend/app/reset-password/page.tsx +0 -11
- data/templates/auth/frontend/app/signup/page.tsx +0 -27
- data/templates/auth/frontend/app/verify-email/page.tsx +0 -11
- data/templates/auth/frontend/components/auth/CredentialsForm.tsx +0 -58
- data/templates/auth/frontend/components/auth/ResetPasswordForm.tsx +0 -43
- data/templates/auth/frontend/components/auth/VerifyEmail.tsx +0 -28
- data/templates/auth/frontend/lib/auth.ts +0 -113
- data/templates/auth/test/controllers/auth_test.rb +0 -108
- data/templates/controller/app/controllers/%file_name%_controller.rb.tt +0 -10
- data/templates/controller/test/controllers/%file_name%_controller_test.rb.tt +0 -14
- data/templates/deploy/Caddyfile.tt +0 -19
- data/templates/deploy/Dockerfile.tt +0 -63
- data/templates/deploy/Procfile.tt +0 -6
- data/templates/deploy/compose.yaml.tt +0 -129
- data/templates/deploy/dot_dockerignore +0 -14
- data/templates/frontend/app/globals.css +0 -171
- data/templates/frontend/app/layout.tsx.tt +0 -19
- data/templates/frontend/app/page.module.css +0 -315
- data/templates/frontend/app/page.tsx.tt +0 -183
- data/templates/frontend/app/providers.tsx +0 -24
- data/templates/frontend/lib/gemstack/client.ts +0 -125
- data/templates/frontend/next-env.d.ts +0 -5
- data/templates/frontend/next.config.ts +0 -17
- data/templates/frontend/package.json.tt +0 -23
- data/templates/frontend/tsconfig.json +0 -21
- data/templates/job/app/jobs/%file_name%.rb.tt +0 -15
- data/templates/job/test/jobs/%file_name%_test.rb.tt +0 -15
- data/templates/migration/db/migrations/%timestamp%_%file_name%.rb.tt +0 -17
- data/templates/policy/app/policies/%file_name%_policy.rb.tt +0 -21
- data/templates/policy/test/policies/%file_name%_policy_test.rb.tt +0 -9
- data/templates/realtime/config/channels.rb +0 -14
- data/templates/realtime/frontend/lib/gemstack/realtime.ts +0 -120
- data/templates/resource/controller/app/controllers/%plural%_controller.rb.tt +0 -51
- data/templates/resource/controller/test/controllers/%plural%_controller_test.rb.tt +0 -74
- data/templates/resource/frontend/frontend/app/%url_segment%/[id]/edit/page.tsx.tt +0 -45
- data/templates/resource/frontend/frontend/app/%url_segment%/[id]/page.tsx.tt +0 -49
- data/templates/resource/frontend/frontend/app/%url_segment%/new/page.tsx.tt +0 -27
- data/templates/resource/frontend/frontend/app/%url_segment%/page.tsx.tt +0 -51
- data/templates/resource/frontend/frontend/components/%url_segment%/%class_name%Card.tsx.tt +0 -15
- data/templates/resource/frontend/frontend/components/%url_segment%/%class_name%Form.tsx.tt +0 -98
- data/templates/resource/frontend/frontend/components/%url_segment%/%class_name%Table.tsx.tt +0 -36
- data/templates/resource/frontend/frontend/lib/format.ts +0 -12
- data/templates/resource/frontend/frontend/lib/queries/%url_segment%.ts.tt +0 -64
- data/templates/resource/migration/db/migrations/%timestamp%_create_%table%.rb.tt +0 -18
- data/templates/resource/model/app/models/%file_name%.rb.tt +0 -15
- data/templates/resource/model/test/models/%file_name%_test.rb.tt +0 -29
- data/templates/resource/serializer/app/serializers/%file_name%_serializer.rb.tt +0 -7
- data/templates/storage/app/controllers/uploads_controller.rb.tt +0 -33
- data/templates/storage/app/serializers/upload_serializer.rb +0 -11
- data/templates/storage/frontend/lib/upload.ts +0 -52
- data/templates/storage/test/controllers/uploads_test.rb.tt +0 -30
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# The base class for this app's models — add plugins, validations and helpers
|
|
4
|
-
# every model should share here. `class Product < ApplicationModel` maps to the
|
|
5
|
-
# products table (docs/models.md).
|
|
6
|
-
#
|
|
7
|
-
# Created with Class.new on purpose: `class ApplicationModel < GemStack::Model`
|
|
8
|
-
# would make Sequel look for an "application_models" table.
|
|
9
|
-
ApplicationModel = Class.new(GemStack::Model) # rubocop:disable Style/EmptyClassDefinition
|
|
10
|
-
|
|
11
|
-
class ApplicationModel
|
|
12
|
-
# Sequel plugins for every model, e.g.:
|
|
13
|
-
# plugin :dirty # previous values after an update (column_changes)
|
|
14
|
-
|
|
15
|
-
# Shared query helpers:
|
|
16
|
-
# dataset_module do
|
|
17
|
-
# def recent = order(Sequel.desc(:created_at))
|
|
18
|
-
# end
|
|
19
|
-
end
|
data/templates/app/bin/gemstack
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
require "gemstack"
|
|
5
|
-
|
|
6
|
-
# Sets the application root and loads .env files (development/test).
|
|
7
|
-
GemStack.setup(root: File.expand_path("..", __dir__))
|
|
8
|
-
|
|
9
|
-
# Require the gems listed in the Gemfile for the current environment.
|
|
10
|
-
Bundler.require(:default, GemStack.env.to_sym)
|
|
11
|
-
|
|
12
|
-
GemStack.configure do |config|
|
|
13
|
-
config.name = "<%= name %>"
|
|
14
|
-
|
|
15
|
-
# Settings for every environment. Everything below is optional: each line
|
|
16
|
-
# shows the default — uncomment one to change it. Per-environment settings go
|
|
17
|
-
# in config/environments/*.rb. All settings: docs/configuration.md.
|
|
18
|
-
|
|
19
|
-
# ── HTTP ────────────────────────────────────────────────────────────────
|
|
20
|
-
# config.http.api_path = "/api" # where the API lives (routes are relative to it)
|
|
21
|
-
# config.http.max_body_size = 10 * 1024 * 1024 # bytes; larger requests get 413
|
|
22
|
-
# config.http.json = :json # or :oj (add gem "oj"), or your own codec
|
|
23
|
-
# config.http.cors.origins = [] # only for frontends on another domain
|
|
24
|
-
# config.http.middleware.use MyMiddleware # add / insert_before / swap / delete
|
|
25
|
-
|
|
26
|
-
# ── Pagination: paginate(...) in controllers, ?page=2&per_page=50 (docs/pagination.md) ──
|
|
27
|
-
# config.http.pagination.per_page = 25 # records per page when the client doesn't say
|
|
28
|
-
# config.http.pagination.max_per_page = 100 # the most a client may ask for with ?per_page=
|
|
29
|
-
|
|
30
|
-
# ── Responses ───────────────────────────────────────────────────────────
|
|
31
|
-
# config.http.compression.enabled = true # gzip/Brotli responses over 1 KB (add gem "brotli" for br)
|
|
32
|
-
# config.http.etags = true # ETag + 304 Not Modified for GET responses
|
|
33
|
-
|
|
34
|
-
# ── Logging ─────────────────────────────────────────────────────────────
|
|
35
|
-
# config.logger.level = :debug # :info in production (or GEMSTACK_LOG_LEVEL)
|
|
36
|
-
# config.filter_parameters += %w[iban] # masked in logs, next to password, token, secret…
|
|
37
|
-
<% if database? -%>
|
|
38
|
-
|
|
39
|
-
# ── Database — the connection is in config/database.yml (DATABASE_URL overrides it) ──
|
|
40
|
-
# config.db.pool_size = 5 # default: GEMSTACK_MAX_THREADS (one per Puma thread)
|
|
41
|
-
# config.db.statement_timeout = 5_000 # ms
|
|
42
|
-
<% end -%>
|
|
43
|
-
|
|
44
|
-
# ── Cache (GemStack.cache) ──────────────────────────────────────────────
|
|
45
|
-
# config.cache.store = :memory # :redis to share it between processes (gem "redis-client")
|
|
46
|
-
# config.cache.default_expires_in = nil # seconds; nil = until evicted
|
|
47
|
-
|
|
48
|
-
# ── Background jobs ─────────────────────────────────────────────────────
|
|
49
|
-
# config.jobs.default_queue = "default"
|
|
50
|
-
# config.jobs.default_max_attempts = 10 # then the job is kept as failed
|
|
51
|
-
|
|
52
|
-
# ── Mail ────────────────────────────────────────────────────────────────
|
|
53
|
-
# config.mail.default_from = "<%= name %> <no-reply@localhost>" # or MAIL_FROM
|
|
54
|
-
end
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
# Database connection (docs/database.md) — the same format as Rails' database.yml.
|
|
2
|
-
# DATABASE_URL (TEST_DATABASE_URL in tests) overrides this file when set, which
|
|
3
|
-
# is what most hosting platforms provide. Adapters: sqlite3, postgresql, mysql2,
|
|
4
|
-
# trilogy (add the matching gem: sqlite3, pg, mysql2, trilogy).
|
|
5
|
-
default: &default
|
|
6
|
-
<% if database_adapter == "sqlite3" -%>
|
|
7
|
-
adapter: sqlite3
|
|
8
|
-
pool: <%%= ENV.fetch("GEMSTACK_MAX_THREADS", 5) %>
|
|
9
|
-
|
|
10
|
-
development:
|
|
11
|
-
<<: *default
|
|
12
|
-
database: db/development.sqlite3
|
|
13
|
-
|
|
14
|
-
test:
|
|
15
|
-
<<: *default
|
|
16
|
-
database: db/test.sqlite3
|
|
17
|
-
|
|
18
|
-
# Keep the file on persistent storage (a volume); or set DATABASE_URL=sqlite3:/data/app.sqlite3.
|
|
19
|
-
production:
|
|
20
|
-
<<: *default
|
|
21
|
-
database: db/production.sqlite3
|
|
22
|
-
<% elsif database_adapter == "postgresql" -%>
|
|
23
|
-
adapter: postgresql
|
|
24
|
-
pool: <%%= ENV.fetch("GEMSTACK_MAX_THREADS", 5) %>
|
|
25
|
-
# Without host/username, the local server is used as your OS user.
|
|
26
|
-
# host: localhost
|
|
27
|
-
# username: <%= database_name %>
|
|
28
|
-
# password: <%%= ENV["DATABASE_PASSWORD"] %>
|
|
29
|
-
|
|
30
|
-
development:
|
|
31
|
-
<<: *default
|
|
32
|
-
database: <%= database_name %>_development
|
|
33
|
-
|
|
34
|
-
test:
|
|
35
|
-
<<: *default
|
|
36
|
-
database: <%= database_name %>_test
|
|
37
|
-
|
|
38
|
-
# In production, DATABASE_URL (postgres://user:password@host:5432/name) usually replaces this.
|
|
39
|
-
production:
|
|
40
|
-
<<: *default
|
|
41
|
-
database: <%= database_name %>_production
|
|
42
|
-
<% else -%>
|
|
43
|
-
adapter: <%= database_adapter %>
|
|
44
|
-
pool: <%%= ENV.fetch("GEMSTACK_MAX_THREADS", 5) %>
|
|
45
|
-
host: <%%= ENV.fetch("DATABASE_HOST", "127.0.0.1") %>
|
|
46
|
-
username: root
|
|
47
|
-
password: <%%= ENV["DATABASE_PASSWORD"] %>
|
|
48
|
-
|
|
49
|
-
development:
|
|
50
|
-
<<: *default
|
|
51
|
-
database: <%= database_name %>_development
|
|
52
|
-
|
|
53
|
-
test:
|
|
54
|
-
<<: *default
|
|
55
|
-
database: <%= database_name %>_test
|
|
56
|
-
|
|
57
|
-
# In production, DATABASE_URL (<%= database_adapter %>://user:password@host:3306/name) usually replaces this.
|
|
58
|
-
production:
|
|
59
|
-
<<: *default
|
|
60
|
-
database: <%= database_name %>_production
|
|
61
|
-
<% end -%>
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Development settings: applied after config/app.rb when GEMSTACK_ENV=development
|
|
4
|
-
# (the default). Each line shows the default value — uncomment one to change it.
|
|
5
|
-
# All settings: docs/configuration.md.
|
|
6
|
-
GemStack.configure do |config|
|
|
7
|
-
# ── Code ────────────────────────────────────────────────────────────────
|
|
8
|
-
# config.reload_code = true # reload app/ and config/routes.rb when they change
|
|
9
|
-
# config.eager_load = false # load app/ lazily, for a faster boot
|
|
10
|
-
# config.jit = nil # :yjit or :zjit to try Ruby's JIT locally
|
|
11
|
-
|
|
12
|
-
# ── Logging ─────────────────────────────────────────────────────────────
|
|
13
|
-
# config.logger.level = :debug # :debug, :info, :warn, :error (or GEMSTACK_LOG_LEVEL)
|
|
14
|
-
# config.logger.format = :pretty # readable lines; :json to see what production logs
|
|
15
|
-
|
|
16
|
-
# ── HTTP ────────────────────────────────────────────────────────────────
|
|
17
|
-
# config.http.show_exceptions = true # 500s carry the exception; browsers get an error page
|
|
18
|
-
# config.contract.docs = true # interactive API docs at /api/docs
|
|
19
|
-
|
|
20
|
-
# ── gemstack dev ────────────────────────────────────────────────────────
|
|
21
|
-
# config.dev.port = 3000 # the one public port (or PORT)
|
|
22
|
-
# config.dev.jobs_command = %w[bundle exec gemstack jobs] # nil: don't start a jobs worker
|
|
23
|
-
<% if database? -%>
|
|
24
|
-
|
|
25
|
-
# ── Database (connection: config/database.yml) ──────────────────────────
|
|
26
|
-
# config.db.log_queries = true # every SQL statement at DEBUG
|
|
27
|
-
# config.db.slow_query_ms = 500 # slower queries are logged at WARN
|
|
28
|
-
<% end -%>
|
|
29
|
-
|
|
30
|
-
# ── Cache ───────────────────────────────────────────────────────────────
|
|
31
|
-
# config.cache.store = :memory # :null to switch caching off, :redis to share it
|
|
32
|
-
|
|
33
|
-
# ── Mail ────────────────────────────────────────────────────────────────
|
|
34
|
-
# config.mail.delivery = :log # saved to tmp/mail (.eml + .html); :smtp really sends
|
|
35
|
-
end
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Production settings: applied after config/app.rb when GEMSTACK_ENV=production.
|
|
4
|
-
# Each line shows the default value — uncomment one to change it.
|
|
5
|
-
#
|
|
6
|
-
# Secrets and connections come from environment variables, never from files:
|
|
7
|
-
# SECRET_KEY_BASE, <% if database? %>DATABASE_URL, <% end %>SMTP_URL, MAIL_FROM, APP_URL, REDIS_URL…
|
|
8
|
-
# `gemstack doctor --production` checks what's missing (docs/deployment.md).
|
|
9
|
-
GemStack.configure do |config|
|
|
10
|
-
# ── Code ────────────────────────────────────────────────────────────────
|
|
11
|
-
# config.eager_load = true # load all code at boot (copy-on-write friendly with Puma workers)
|
|
12
|
-
# config.jit = :yjit # Ruby's JIT (or GEMSTACK_JIT=yjit|zjit|off)
|
|
13
|
-
|
|
14
|
-
# ── Logging ─────────────────────────────────────────────────────────────
|
|
15
|
-
# config.logger.level = :info
|
|
16
|
-
# config.logger.format = :json # one JSON object per line, with the request id
|
|
17
|
-
|
|
18
|
-
# ── HTTP ────────────────────────────────────────────────────────────────
|
|
19
|
-
# config.http.show_exceptions = false # never send exception details to clients
|
|
20
|
-
# config.http.hsts = "max-age=63072000; includeSubDomains" # on HTTPS requests; nil if your proxy sets it
|
|
21
|
-
# config.http.cors.origins = ["https://app.example.com"] # only if the frontend is on another domain
|
|
22
|
-
# config.http.max_body_size = 10 * 1024 * 1024 # larger requests get 413
|
|
23
|
-
<% if database? -%>
|
|
24
|
-
|
|
25
|
-
# ── Database ────────────────────────────────────────────────────────────
|
|
26
|
-
# config.db.pool_size = 5 # connections per process (GEMSTACK_MAX_THREADS)
|
|
27
|
-
# config.db.statement_timeout = 5_000 # ms; stop runaway queries (PostgreSQL, MySQL)
|
|
28
|
-
# config.db.slow_query_ms = 500 # slower queries are logged at WARN
|
|
29
|
-
<% end -%>
|
|
30
|
-
|
|
31
|
-
# ── Cache: :memory is per process — use Redis with several processes or servers ──
|
|
32
|
-
# config.cache.store = :redis # add gem "redis-client"; REDIS_URL
|
|
33
|
-
|
|
34
|
-
# ── Background jobs (`gemstack jobs`) ───────────────────────────────────
|
|
35
|
-
# config.jobs.concurrency = 5 # threads per worker (or GEMSTACK_JOB_CONCURRENCY)
|
|
36
|
-
|
|
37
|
-
# ── Mail ────────────────────────────────────────────────────────────────
|
|
38
|
-
# config.mail.delivery = :smtp # SMTP_URL=smtp://user:password@smtp.example.com:587
|
|
39
|
-
# config.mail.default_from = "App <hello@example.com>" # or MAIL_FROM
|
|
40
|
-
end
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Test settings: applied after config/app.rb when tests run (`gemstack test`).
|
|
4
|
-
# Each line shows the default value — uncomment one to change it.
|
|
5
|
-
GemStack.configure do |config|
|
|
6
|
-
# ── Logging ─────────────────────────────────────────────────────────────
|
|
7
|
-
# config.logger.level = :debug # logs are discarded in tests unless this (or GEMSTACK_LOG_LEVEL) is set
|
|
8
|
-
|
|
9
|
-
# ── HTTP ────────────────────────────────────────────────────────────────
|
|
10
|
-
# config.http.show_exceptions = true # a failing request's JSON includes the exception
|
|
11
|
-
<% if database? -%>
|
|
12
|
-
|
|
13
|
-
# ── Database (connection: the test section of config/database.yml, or TEST_DATABASE_URL) ──
|
|
14
|
-
# config.db.log_queries = false
|
|
15
|
-
<% end -%>
|
|
16
|
-
|
|
17
|
-
# ── Background work is recorded, not performed ──────────────────────────
|
|
18
|
-
# config.jobs.adapter = :test # assert_enqueued / perform_enqueued_jobs; :inline runs jobs at once
|
|
19
|
-
# config.mail.delivery = :test # assert_emails / last_email
|
|
20
|
-
# config.cache.store = :null # no caching, so tests can't leak into each other; :memory to test it
|
|
21
|
-
end
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Puma configuration. Every value can be set through the environment.
|
|
4
|
-
# `gemstack dev` sets GEMSTACK_API_HOST/PORT to a private loopback port
|
|
5
|
-
# behind the development gateway.
|
|
6
|
-
|
|
7
|
-
max_threads = Integer(ENV.fetch("GEMSTACK_MAX_THREADS", 5))
|
|
8
|
-
min_threads = Integer(ENV.fetch("GEMSTACK_MIN_THREADS", max_threads))
|
|
9
|
-
threads min_threads, max_threads
|
|
10
|
-
|
|
11
|
-
bind "tcp://#{ENV.fetch("GEMSTACK_API_HOST", "0.0.0.0")}:#{ENV.fetch("GEMSTACK_API_PORT", 4000)}"
|
|
12
|
-
environment ENV.fetch("GEMSTACK_ENV", "development")
|
|
13
|
-
|
|
14
|
-
# Cluster mode for multi-core production servers: WEB_CONCURRENCY=4
|
|
15
|
-
worker_count = Integer(ENV.fetch("WEB_CONCURRENCY", 0))
|
|
16
|
-
if worker_count.positive?
|
|
17
|
-
workers worker_count
|
|
18
|
-
preload_app!
|
|
19
|
-
# Connections must not be shared across forked workers; each reconnects lazily.
|
|
20
|
-
before_fork { GemStack::DB.disconnect if defined?(GemStack::DB) }
|
|
21
|
-
end
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# API routes, relative to config.http.api_path ("/api"):
|
|
4
|
-
#
|
|
5
|
-
# get "/status", to: "status#show" # GET /api/status → StatusController#show
|
|
6
|
-
# resources :products # /api/products: index, create, show, update, destroy
|
|
7
|
-
#
|
|
8
|
-
# List them with `gemstack routes`. In development this file reloads on save.
|
|
9
|
-
GemStack.routes do
|
|
10
|
-
end
|
data/templates/app/config.ru
DELETED
|
File without changes
|
data/templates/app/db/seeds.rb
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# Copy to .env for local development overrides. Never commit real secrets.
|
|
2
|
-
# Variables already set in your shell always take precedence.
|
|
3
|
-
|
|
4
|
-
# PORT=3000 # public development port (gemstack dev)
|
|
5
|
-
# GEMSTACK_LOG_LEVEL=debug
|
|
6
|
-
|
|
7
|
-
<% if database? -%>
|
|
8
|
-
# Database: config/database.yml. A URL here overrides it (hosting platforms set DATABASE_URL).
|
|
9
|
-
<% if database_adapter == "sqlite3" -%>
|
|
10
|
-
# DATABASE_URL=sqlite3:db/development.sqlite3
|
|
11
|
-
<% elsif database_adapter == "postgresql" -%>
|
|
12
|
-
# DATABASE_URL=postgres://user:password@localhost:5432/<%= database_name %>_development
|
|
13
|
-
# TEST_DATABASE_URL=postgres://user:password@localhost:5432/<%= database_name %>_test
|
|
14
|
-
<% else -%>
|
|
15
|
-
# DATABASE_PASSWORD=
|
|
16
|
-
# DATABASE_URL=<%= database_adapter %>://root:password@127.0.0.1:3306/<%= database_name %>_development
|
|
17
|
-
# TEST_DATABASE_URL=<%= database_adapter %>://root:password@127.0.0.1:3306/<%= database_name %>_test
|
|
18
|
-
<% end -%>
|
|
19
|
-
<% end -%>
|
data/templates/app/dot_gitignore
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<%= node_version %>
|
data/templates/app/dot_nvmrc.tt
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<%= node_version %>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<%= ruby_version %>
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "test_helper"
|
|
4
|
-
|
|
5
|
-
class HealthTest < GemStack::TestCase
|
|
6
|
-
def test_api_boots_and_answers_health_checks
|
|
7
|
-
get_json "/api/health"
|
|
8
|
-
|
|
9
|
-
assert_status 200
|
|
10
|
-
assert_equal({ "status" => "ok" }, json_body)
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def test_unknown_routes_return_json_errors
|
|
14
|
-
get_json "/api/does-not-exist"
|
|
15
|
-
|
|
16
|
-
assert_error 404, "route_not_found"
|
|
17
|
-
end
|
|
18
|
-
end
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
ENV["GEMSTACK_ENV"] = "test"
|
|
4
|
-
|
|
5
|
-
require_relative "../config/app"
|
|
6
|
-
require "gemstack/testing"
|
|
7
|
-
require "gemstack/jobs/testing"
|
|
8
|
-
<% if database? -%>
|
|
9
|
-
require "gemstack/db/testing"
|
|
10
|
-
<% end -%>
|
|
11
|
-
require "minitest/autorun"
|
|
12
|
-
|
|
13
|
-
<% if database? -%>
|
|
14
|
-
# Creates the test database (TEST_DATABASE_URL) if needed and applies migrations.
|
|
15
|
-
GemStack::DB::Testing.prepare!
|
|
16
|
-
<% end -%>
|
|
17
|
-
GemStack.boot!
|
|
18
|
-
<% if database? -%>
|
|
19
|
-
|
|
20
|
-
# Every test runs in a transaction that is rolled back afterwards.
|
|
21
|
-
GemStack::TestCase.include GemStack::DB::Testing::Transactions
|
|
22
|
-
<% end -%>
|
|
23
|
-
|
|
24
|
-
# Jobs are recorded, not run: assert_enqueued / perform_enqueued_jobs.
|
|
25
|
-
GemStack::TestCase.include GemStack::Jobs::Testing
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Personal API tokens: send `Authorization: Bearer <token>` instead of a cookie.
|
|
4
|
-
# GET /api/auth/tokens · POST /api/auth/tokens · DELETE /api/auth/tokens/:id
|
|
5
|
-
class ApiTokensController < ApplicationController
|
|
6
|
-
before :require_login
|
|
7
|
-
before :require_session, only: %i[create destroy] # a leaked token can't mint or revoke tokens
|
|
8
|
-
|
|
9
|
-
accepts :create do
|
|
10
|
-
required :name, :string, max_length: 100
|
|
11
|
-
end
|
|
12
|
-
returns :index, [ApiTokenSerializer]
|
|
13
|
-
returns :create, NewApiTokenSerializer
|
|
14
|
-
|
|
15
|
-
def index = render(current_user.auth_tokens_dataset.api.order(Sequel.desc(:created_at)), serializer: ApiTokenSerializer)
|
|
16
|
-
|
|
17
|
-
def create
|
|
18
|
-
token, id = GemStack::Auth::Tokens.issue(current_user.id, purpose: "api", name: input[:name])
|
|
19
|
-
render NewApiTokenSerializer::Result.new(token: token, api_token: AuthToken[id]),
|
|
20
|
-
status: :created, serializer: NewApiTokenSerializer
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def destroy
|
|
24
|
-
revoked = GemStack::Auth::Tokens.revoke(params[:id], user_id: current_user.id)
|
|
25
|
-
raise GemStack::NotFound, "API token not found" unless revoked
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
private
|
|
29
|
-
|
|
30
|
-
def require_session
|
|
31
|
-
return if authenticated_by == :session
|
|
32
|
-
|
|
33
|
-
raise GemStack::Forbidden.new("Manage API tokens from the app.", code: "session_required")
|
|
34
|
-
end
|
|
35
|
-
end
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# POST /api/auth/email/resend · POST /api/auth/email/verify
|
|
4
|
-
class EmailVerificationsController < ApplicationController
|
|
5
|
-
before :require_login, only: :create
|
|
6
|
-
rate_limit to: 5, within: 3600, only: :create
|
|
7
|
-
rate_limit to: 20, within: 3600, only: :update
|
|
8
|
-
|
|
9
|
-
accepts :update do
|
|
10
|
-
required :token, :string, max_length: 200
|
|
11
|
-
end
|
|
12
|
-
returns :create, nil
|
|
13
|
-
returns :update, nil
|
|
14
|
-
|
|
15
|
-
def create
|
|
16
|
-
AuthMailer.email_verification(current_user.id).deliver_later unless current_user.email_verified?
|
|
17
|
-
head :accepted
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
# Works signed in or out: the link may be opened on another device.
|
|
21
|
-
def update
|
|
22
|
-
row = GemStack::Auth::Tokens.consume(input[:token], purpose: "email_verification")
|
|
23
|
-
user = row && User[row[:user_id]]
|
|
24
|
-
# Only valid for the address it was sent to.
|
|
25
|
-
unless user && user.email == row[:email]
|
|
26
|
-
raise GemStack::BadRequest.new("This verification link is invalid or has expired.", code: "invalid_token")
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
user.update(email_verified_at: Time.now) unless user.email_verified?
|
|
30
|
-
end
|
|
31
|
-
end
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# POST /api/auth/password/forgot · POST /api/auth/password/reset
|
|
4
|
-
class PasswordResetsController < ApplicationController
|
|
5
|
-
rate_limit to: 10, within: 3600, only: :create
|
|
6
|
-
rate_limit to: 3, within: 3600, only: :create, name: "reset-email", by: -> { params[:email].to_s.downcase }
|
|
7
|
-
rate_limit to: 10, within: 3600, only: :update
|
|
8
|
-
|
|
9
|
-
accepts :create do
|
|
10
|
-
required :email, :string, max_length: 254
|
|
11
|
-
end
|
|
12
|
-
accepts :update do
|
|
13
|
-
required :token, :string, max_length: 200
|
|
14
|
-
required :password, :string, max_length: 1024
|
|
15
|
-
end
|
|
16
|
-
returns :create, nil
|
|
17
|
-
returns :update, UserSerializer
|
|
18
|
-
|
|
19
|
-
# Always 202: whether an account exists is nobody else's business.
|
|
20
|
-
def create
|
|
21
|
-
user = User.first(email: User.normalize_email(input[:email]))
|
|
22
|
-
AuthMailer.password_reset(user.id).deliver_later if user
|
|
23
|
-
head :accepted
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def update
|
|
27
|
-
errors = GemStack::Auth::Password.errors(input[:password])
|
|
28
|
-
raise GemStack::ValidationError.new(errors: { password: errors }) unless errors.empty?
|
|
29
|
-
|
|
30
|
-
row = GemStack::Auth::Tokens.consume(input[:token], purpose: "password_reset")
|
|
31
|
-
raise GemStack::BadRequest.new("This reset link is invalid or has expired.", code: "invalid_token") unless row
|
|
32
|
-
|
|
33
|
-
user = User[row[:user_id]]
|
|
34
|
-
user.update(password: input[:password])
|
|
35
|
-
# A reset usually means the old password leaked: end every session.
|
|
36
|
-
GemStack::Auth::Sessions.revoke_all(user.id)
|
|
37
|
-
sign_in(user)
|
|
38
|
-
render user
|
|
39
|
-
end
|
|
40
|
-
end
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# POST /api/auth/signup
|
|
4
|
-
class RegistrationsController < ApplicationController
|
|
5
|
-
rate_limit to: 10, within: 3600, only: :create
|
|
6
|
-
|
|
7
|
-
accepts :create do
|
|
8
|
-
required :email, :string, max_length: 254
|
|
9
|
-
required :password, :string, max_length: 1024
|
|
10
|
-
end
|
|
11
|
-
returns :create, UserSerializer
|
|
12
|
-
|
|
13
|
-
def create
|
|
14
|
-
user = User.create(email: input[:email], password: input[:password]) # invalid: 422 with field errors
|
|
15
|
-
AuthMailer.email_verification(user.id).deliver_later
|
|
16
|
-
sign_in(user)
|
|
17
|
-
render user, status: :created
|
|
18
|
-
end
|
|
19
|
-
end
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# POST /api/auth/login · GET /api/auth/me · DELETE /api/auth/logout
|
|
4
|
-
class SessionsController < ApplicationController
|
|
5
|
-
before :require_login, only: :show
|
|
6
|
-
# Per IP, and per account so one address can't be guessed at from many IPs.
|
|
7
|
-
rate_limit to: 20, within: 300, only: :create
|
|
8
|
-
rate_limit to: 10, within: 300, only: :create, name: "login-email", by: -> { params[:email].to_s.downcase }
|
|
9
|
-
|
|
10
|
-
accepts :create do
|
|
11
|
-
required :email, :string, max_length: 254
|
|
12
|
-
required :password, :string, max_length: 1024
|
|
13
|
-
end
|
|
14
|
-
returns :create, UserSerializer
|
|
15
|
-
returns :show, UserSerializer
|
|
16
|
-
|
|
17
|
-
def create
|
|
18
|
-
user = User.authenticate_by(email: input[:email], password: input[:password])
|
|
19
|
-
# The same answer for "no such account" and "wrong password".
|
|
20
|
-
raise GemStack::Unauthorized.new("Invalid email or password.", code: "invalid_credentials") unless user
|
|
21
|
-
|
|
22
|
-
sign_in(user)
|
|
23
|
-
render user
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def show = render(current_user)
|
|
27
|
-
|
|
28
|
-
def destroy = sign_out
|
|
29
|
-
end
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Account emails. Templates: app/mailers/templates/auth_mailer/*.erb.
|
|
4
|
-
# Links point at the Next.js pages (config.auth.app_url, APP_URL in production).
|
|
5
|
-
class AuthMailer < ApplicationMailer
|
|
6
|
-
def password_reset(user_id)
|
|
7
|
-
@user = User[user_id] or return
|
|
8
|
-
token, = GemStack::Auth::Tokens.issue(@user.id, purpose: "password_reset")
|
|
9
|
-
@url = GemStack::Auth.url("/reset-password", token: token)
|
|
10
|
-
@minutes = GemStack.config.auth.password_reset_ttl / 60
|
|
11
|
-
mail to: @user.email, subject: "Reset your password"
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def email_verification(user_id)
|
|
15
|
-
@user = User[user_id] or return
|
|
16
|
-
token, = GemStack::Auth::Tokens.issue(@user.id, purpose: "email_verification", email: @user.email)
|
|
17
|
-
@url = GemStack::Auth.url("/verify-email", token: token)
|
|
18
|
-
mail to: @user.email, subject: "Confirm your email address"
|
|
19
|
-
end
|
|
20
|
-
end
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# API tokens (and single-use reset/verification tokens) — see GemStack::Auth::Tokens.
|
|
4
|
-
class AuthToken < ApplicationModel
|
|
5
|
-
field :purpose, :string, null: false
|
|
6
|
-
field :name, :string, size: 100
|
|
7
|
-
field :last_used_at, :datetime
|
|
8
|
-
field :expires_at, :datetime
|
|
9
|
-
|
|
10
|
-
belongs_to :user
|
|
11
|
-
|
|
12
|
-
dataset_module do
|
|
13
|
-
def api = where(purpose: "api")
|
|
14
|
-
end
|
|
15
|
-
end
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Accounts. GemStack::Auth::User adds password hashing (Argon2id), email
|
|
4
|
-
# normalization, validations and User.authenticate_by(email:, password:).
|
|
5
|
-
class User < ApplicationModel
|
|
6
|
-
include GemStack::Auth::User
|
|
7
|
-
|
|
8
|
-
field :email, :string, null: false, size: 254
|
|
9
|
-
field :email_verified_at, :datetime
|
|
10
|
-
|
|
11
|
-
has_many :auth_tokens
|
|
12
|
-
end
|