studio-engine 0.8.0 → 0.10.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 +39 -0
- data/app/models/concerns/studio/broadcastable.rb +40 -0
- data/app/models/studio/enumeral.rb +85 -0
- data/db/migrate/20260623130000_create_studio_enumerals.rb +30 -0
- data/lib/studio/cable.rb +35 -0
- data/lib/studio/redis.rb +41 -0
- data/lib/studio/version.rb +1 -1
- data/lib/studio.rb +2 -0
- data/studio-engine.gemspec +5 -0
- metadata +36 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 20b0e58f17421ce741452b329000bc657f0dba0304198d7dc1fb59cb448bfc6d
|
|
4
|
+
data.tar.gz: d4446a5bad4b0b744e54ad7d4f275cf77455a68f2cf3289d6d9c472ca1f3c548
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b15b1f884815e8afbad3d9aa181589230193942970d62457075d08c47521c967a005025560e665b436ceb8a6c78f425688430f122cad9a7f21e8a4432bd4ca22
|
|
7
|
+
data.tar.gz: 5a6c6817018309298bb3a8eb686f61d7e93d9718d5b8ce5b5a8e642efc82f99695d6bf0c4f23b9421fa8c65481722e9164934c7cbe47f254e3abb482d7ea42ed
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,45 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.10.0 — 2026-06-24
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **Shared websocket / Redis primitive** (`docs/CABLE.md`) — one place for the
|
|
11
|
+
setup every host app's realtime needs, extracted after a SEV-1 (a host app
|
|
12
|
+
shipped an ActionCable channel with no `redis` gem and no TLS cable config; the
|
|
13
|
+
broadcast raised `Gem::LoadError` — a `ScriptError`, not a `StandardError` — which
|
|
14
|
+
escaped a `rescue StandardError` and 500'd every task write).
|
|
15
|
+
- **`redis` is now an engine dependency** (plus `turbo-rails`), so a consuming app
|
|
16
|
+
can never hit that `Gem::LoadError` again.
|
|
17
|
+
- **`Studio::Redis`** — the single source of Redis connection truth for `cable.yml`,
|
|
18
|
+
the `cache_store`, and Sidekiq: `.url`, `.tls?`, and `.options(**extra)`, which
|
|
19
|
+
auto-applies `ssl_params: { verify_mode: VERIFY_NONE }` for Heroku's `rediss://`
|
|
20
|
+
self-signed TLS (the gotcha that silently drops every broadcast).
|
|
21
|
+
- **`Studio::Cable.safe_broadcast { … }`** — best-effort broadcast guard that
|
|
22
|
+
catches `StandardError` **and** `ScriptError`, so a cable failure never breaks
|
|
23
|
+
the caller (the exact hole that caused the SEV-1).
|
|
24
|
+
- **`Studio::Broadcastable`** — model concern with safe Turbo-Streams wrappers
|
|
25
|
+
(`safe_broadcast_replace_to`, `_append_to`, `_prepend_to`, `_update_to`,
|
|
26
|
+
`_remove_to`) every app should broadcast through.
|
|
27
|
+
|
|
28
|
+
## 0.9.0 — 2026-06-23
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- **`Studio::Enumeral`** — a shared, DB-backed enumeration table for the whole
|
|
32
|
+
ecosystem. Every "list of fixed, labeled, colored values" (Pokémon types first;
|
|
33
|
+
statuses/tiers/roles later) is rows in ONE `studio_enumerals` table, grouped by
|
|
34
|
+
`category`: a stable machine `key`, a human `label`, a `color` hex, a display
|
|
35
|
+
`position`, a sparse `rank` (gappy 100/200/… domain ranking — e.g. most-common
|
|
36
|
+
first — distinct from display order), and `metadata` (jsonb) for
|
|
37
|
+
category-specific extras. Class helpers `catalog`, `lookup`, `color_map` (one
|
|
38
|
+
query → `{ key => color }`), `color_for(category, key, fallback:)`, and a
|
|
39
|
+
metadata-backed `emoji` reader + `emoji_map` (decorative glyphs live in
|
|
40
|
+
`metadata`, not a column); scopes `ordered` / `by_rank`; `available?` degrades
|
|
41
|
+
to empty when the table isn't installed. Shipped by the gem like `Studio::Link` /
|
|
42
|
+
`Studio::EmailDelivery` — reference migration `create_studio_enumerals`
|
|
43
|
+
installed per app (copy into `db/migrate`). No behavior is attached: a consumer
|
|
44
|
+
adopts a new category by seeding rows, no code change.
|
|
45
|
+
|
|
7
46
|
## 0.8.0 — 2026-06-21
|
|
8
47
|
|
|
9
48
|
### Added
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Studio
|
|
4
|
+
# Mixin for ActiveRecord models that broadcast Turbo Streams. It wraps
|
|
5
|
+
# turbo-rails' broadcast_*_to methods in Studio::Cable.safe_broadcast, so a cable
|
|
6
|
+
# failure (a Redis hiccup, a missing/misconfigured adapter) can NEVER break the
|
|
7
|
+
# model save / after_commit that triggered the broadcast — the SEV-1 guard, in
|
|
8
|
+
# one place. The host model already has the raw broadcast_*_to methods (turbo-rails
|
|
9
|
+
# includes Turbo::Broadcastable into ActiveRecord::Base); these are the SAFE
|
|
10
|
+
# variants every app should broadcast through.
|
|
11
|
+
#
|
|
12
|
+
# class Task < ApplicationRecord
|
|
13
|
+
# include Studio::Broadcastable
|
|
14
|
+
# after_create_commit { safe_broadcast_replace_to [:board], target: "card_#{id}",
|
|
15
|
+
# partial: "tasks/card", locals: { task: self } }
|
|
16
|
+
# end
|
|
17
|
+
module Broadcastable
|
|
18
|
+
extend ActiveSupport::Concern
|
|
19
|
+
|
|
20
|
+
def safe_broadcast_replace_to(*args, **kwargs, &block)
|
|
21
|
+
Studio::Cable.safe_broadcast { broadcast_replace_to(*args, **kwargs, &block) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def safe_broadcast_update_to(*args, **kwargs, &block)
|
|
25
|
+
Studio::Cable.safe_broadcast { broadcast_update_to(*args, **kwargs, &block) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def safe_broadcast_append_to(*args, **kwargs, &block)
|
|
29
|
+
Studio::Cable.safe_broadcast { broadcast_append_to(*args, **kwargs, &block) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def safe_broadcast_prepend_to(*args, **kwargs, &block)
|
|
33
|
+
Studio::Cable.safe_broadcast { broadcast_prepend_to(*args, **kwargs, &block) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def safe_broadcast_remove_to(*args, **kwargs, &block)
|
|
37
|
+
Studio::Cable.safe_broadcast { broadcast_remove_to(*args, **kwargs, &block) }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
module Studio
|
|
2
|
+
# A shared, DB-backed enumeration table for the whole ecosystem. Every "list of
|
|
3
|
+
# fixed, labeled, colored values" — Pokémon types (its first use), and later
|
|
4
|
+
# statuses, tiers, roles, … — is just rows in ONE studio_enumerals table,
|
|
5
|
+
# grouped by `category`. Each row is identity + presentation: a stable machine
|
|
6
|
+
# `key` ("fire"), a human `label` ("Fire"), a `color` hex ("#EE8130"), and a
|
|
7
|
+
# `position` for ordering within its category. `metadata` (jsonb) carries any
|
|
8
|
+
# category-specific extras off the columns.
|
|
9
|
+
#
|
|
10
|
+
# Shipped by the gem like Studio::Link / Studio::EmailDelivery: the model lives
|
|
11
|
+
# here, the table lives in each consumer app (copy the reference migration into
|
|
12
|
+
# db/migrate). No behavior is attached — it is pure reference data the apps
|
|
13
|
+
# read, so a consumer adopts a new category by seeding rows, no code change.
|
|
14
|
+
class Enumeral < ApplicationRecord
|
|
15
|
+
self.table_name = "studio_enumerals"
|
|
16
|
+
|
|
17
|
+
HEX = /\A#(?:\h{3}|\h{6})\z/
|
|
18
|
+
|
|
19
|
+
validates :category, presence: true
|
|
20
|
+
validates :key, presence: true,
|
|
21
|
+
uniqueness: { scope: :category, case_sensitive: false }
|
|
22
|
+
validates :color, format: { with: HEX, message: "must be a hex color like #EE8130" },
|
|
23
|
+
allow_blank: true
|
|
24
|
+
|
|
25
|
+
scope :in_category, ->(category) { where(category: category.to_s) }
|
|
26
|
+
scope :ordered, -> { order(:position, :key) }
|
|
27
|
+
# By `rank` (a sparse 100/200/… domain ranking), nulls last — for categories
|
|
28
|
+
# ordered by something other than display position, e.g. most-common-first.
|
|
29
|
+
scope :by_rank, -> { order(:rank) }
|
|
30
|
+
|
|
31
|
+
class << self
|
|
32
|
+
# True only when the table is actually migrated in this app's DB, so a
|
|
33
|
+
# consumer that hasn't installed the migration degrades to "empty" instead
|
|
34
|
+
# of crashing (mirrors Studio::EmailDelivery.available?).
|
|
35
|
+
def available?
|
|
36
|
+
connection.data_source_exists?(table_name)
|
|
37
|
+
rescue ActiveRecord::ActiveRecordError, NoMethodError
|
|
38
|
+
false
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# The ordered list of a category's enumerals — the relation a view iterates
|
|
42
|
+
# (one query). Returns an empty relation when the table isn't installed yet,
|
|
43
|
+
# so callers can `.each` safely pre-migration.
|
|
44
|
+
def catalog(category)
|
|
45
|
+
return none unless available?
|
|
46
|
+
|
|
47
|
+
in_category(category).ordered
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# The single enumeral for (category, key), or nil.
|
|
51
|
+
def lookup(category, key)
|
|
52
|
+
return nil unless available?
|
|
53
|
+
|
|
54
|
+
in_category(category).find_by(key: key.to_s)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# { key => color } for a category in ONE query — the shape a view wants:
|
|
58
|
+
# build it once, then look up each cell with no extra queries (avoids an
|
|
59
|
+
# N+1 when rendering many rows, e.g. the /pokemon type badges).
|
|
60
|
+
def color_map(category)
|
|
61
|
+
catalog(category).pluck(:key, :color).to_h
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Just the color for a single (category, key), with an optional fallback
|
|
65
|
+
# when the key is unknown or the table isn't installed yet.
|
|
66
|
+
def color_for(category, key, fallback: nil)
|
|
67
|
+
return fallback unless available?
|
|
68
|
+
|
|
69
|
+
in_category(category).where(key: key.to_s).limit(1).pick(:color) || fallback
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# { key => emoji } for a category in ONE query — the metadata sibling of
|
|
73
|
+
# color_map, for decorative glyphs kept in `metadata` rather than a column.
|
|
74
|
+
def emoji_map(category)
|
|
75
|
+
catalog(category).pluck(:key, :metadata).to_h { |key, meta| [key, meta && meta["emoji"]] }
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# A decorative glyph for the value, kept in `metadata` (not a column) since
|
|
80
|
+
# it's a presentation extra — e.g. a Pokémon type's emoji. Nil when unset.
|
|
81
|
+
def emoji
|
|
82
|
+
metadata && metadata["emoji"]
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Reference migration for the Studio::Enumeral model. Like the engine's
|
|
2
|
+
# studio_links / studio_email_deliveries migrations, each consumer app installs
|
|
3
|
+
# its own copy of this into db/migrate so the table is created in the app's
|
|
4
|
+
# database (the model is shipped by the gem; the table lives per app).
|
|
5
|
+
class CreateStudioEnumerals < ActiveRecord::Migration[7.2]
|
|
6
|
+
def change
|
|
7
|
+
create_table :studio_enumerals do |t|
|
|
8
|
+
# `category` groups a fixed set of values (e.g. "pokemon_type"); `key` is
|
|
9
|
+
# the stable machine value within it (e.g. "fire"). label/color/position
|
|
10
|
+
# are presentation; metadata (jsonb) carries any category-specific extras.
|
|
11
|
+
t.string :category, null: false
|
|
12
|
+
t.string :key, null: false
|
|
13
|
+
t.string :label
|
|
14
|
+
t.string :color
|
|
15
|
+
t.integer :position, null: false, default: 0
|
|
16
|
+
# A sparse, gappy ordinal (100, 200, …) for a domain ranking distinct from
|
|
17
|
+
# display `position` — e.g. Pokémon types ranked most-common → least-common.
|
|
18
|
+
# The gaps leave room to insert a value between two ranks without
|
|
19
|
+
# renumbering. Nullable: not every enumeral is ranked.
|
|
20
|
+
t.integer :rank
|
|
21
|
+
t.jsonb :metadata, null: false, default: {}
|
|
22
|
+
|
|
23
|
+
t.timestamps
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
add_index :studio_enumerals, [:category, :key], unique: true
|
|
27
|
+
add_index :studio_enumerals, [:category, :position]
|
|
28
|
+
add_index :studio_enumerals, [:category, :rank]
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/studio/cable.rb
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "redis"
|
|
4
|
+
|
|
5
|
+
module Studio
|
|
6
|
+
# Shared ActionCable / Turbo-Streams helpers for host apps.
|
|
7
|
+
module Cable
|
|
8
|
+
# Run a broadcast best-effort: a cable failure must NEVER break the caller (the
|
|
9
|
+
# model save / task write that triggered it). ScriptError is caught ON PURPOSE —
|
|
10
|
+
# a missing or misconfigured cable adapter raises Gem::LoadError, which is a
|
|
11
|
+
# ScriptError, NOT a StandardError. A plain `rescue StandardError` let exactly
|
|
12
|
+
# that escape an after_commit and 500 every task write in production (the SEV-1
|
|
13
|
+
# this primitive exists to prevent). Failures are captured to ErrorLog when the
|
|
14
|
+
# host defines it, else logged. Returns nil on failure.
|
|
15
|
+
#
|
|
16
|
+
# The never-raise guarantee is ABSOLUTE: even logging the failure must not break
|
|
17
|
+
# the caller. ErrorLog.capture! writes to the DB and can itself raise (e.g.
|
|
18
|
+
# ActiveRecord::NoDatabaseError when the DB is down), so the logging is wrapped
|
|
19
|
+
# in its own rescue — the guard cannot be defeated by its own error path.
|
|
20
|
+
def self.safe_broadcast
|
|
21
|
+
yield
|
|
22
|
+
rescue StandardError, ScriptError => e
|
|
23
|
+
begin
|
|
24
|
+
if defined?(ErrorLog) && ErrorLog.respond_to?(:capture!)
|
|
25
|
+
ErrorLog.capture!(e)
|
|
26
|
+
elsif defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
|
27
|
+
Rails.logger.warn("[studio-cable] broadcast failed (non-fatal): #{e.class}: #{e.message}")
|
|
28
|
+
end
|
|
29
|
+
rescue StandardError, ScriptError
|
|
30
|
+
nil # logging is best-effort too — the never-raise guarantee is absolute
|
|
31
|
+
end
|
|
32
|
+
nil
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/studio/redis.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "openssl"
|
|
4
|
+
|
|
5
|
+
module Studio
|
|
6
|
+
# Single source of Redis connection truth for EVERY Redis client in a host app —
|
|
7
|
+
# ActionCable (config/cable.yml), the Rails cache_store, and Sidekiq. It bakes in
|
|
8
|
+
# the Heroku gotcha so no app re-hits it:
|
|
9
|
+
#
|
|
10
|
+
# Heroku Redis serves rediss:// (TLS) with a SELF-SIGNED cert. redis-client
|
|
11
|
+
# verifies peer certs by default, so the connection is silently REJECTED — and
|
|
12
|
+
# because ActionCable's pubsub failure is silent (the /cable socket still
|
|
13
|
+
# upgrades to 101), broadcasts simply never reach subscribers; the cache no-ops.
|
|
14
|
+
# The fix is ssl_params verify_mode VERIFY_NONE, applied automatically here for
|
|
15
|
+
# any rediss:// URL.
|
|
16
|
+
#
|
|
17
|
+
# Usage:
|
|
18
|
+
# cable.yml -> Studio::Redis.options (url + ssl_params)
|
|
19
|
+
# cache_store -> Studio::Redis.options(namespace: "x", expires_in: 90.minutes)
|
|
20
|
+
# sidekiq -> Studio::Redis.options
|
|
21
|
+
module Redis
|
|
22
|
+
# The Redis URL from the environment, with a local-dev fallback.
|
|
23
|
+
def self.url(default = "redis://localhost:6379/1")
|
|
24
|
+
ENV.fetch("REDIS_URL", default)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# True when the endpoint is TLS (Heroku Redis), i.e. a rediss:// URL.
|
|
28
|
+
def self.tls?(redis_url = url)
|
|
29
|
+
redis_url.to_s.start_with?("rediss://")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Connection options for a Redis client: the url, plus — for a TLS endpoint —
|
|
33
|
+
# the self-signed-cert handling Heroku requires. Caller extras (namespace,
|
|
34
|
+
# reconnect_attempts, error_handler, …) merge through untouched.
|
|
35
|
+
def self.options(redis_url: url, **extra)
|
|
36
|
+
opts = { url: redis_url }.merge(extra)
|
|
37
|
+
opts[:ssl_params] = { verify_mode: OpenSSL::SSL::VERIFY_NONE } if tls?(redis_url)
|
|
38
|
+
opts
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/studio/version.rb
CHANGED
data/lib/studio.rb
CHANGED
data/studio-engine.gemspec
CHANGED
|
@@ -28,4 +28,9 @@ Gem::Specification.new do |spec|
|
|
|
28
28
|
spec.add_dependency "aws-sdk-s3", "~> 1.218"
|
|
29
29
|
spec.add_dependency "mini_magick", "~> 5.0"
|
|
30
30
|
spec.add_dependency "resend", "~> 1.1"
|
|
31
|
+
# Realtime: the redis cable/cache/Sidekiq adapter (Studio::Redis) + Turbo Streams
|
|
32
|
+
# broadcasting (Studio::Broadcastable). `redis` is the dependency whose ABSENCE
|
|
33
|
+
# 500'd a host app's task board — declaring it here makes that impossible to repeat.
|
|
34
|
+
spec.add_dependency "redis", ">= 4.0.1"
|
|
35
|
+
spec.add_dependency "turbo-rails", ">= 1.0"
|
|
31
36
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: studio-engine
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alex McRitchie
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: rails
|
|
@@ -126,6 +125,34 @@ dependencies:
|
|
|
126
125
|
- - "~>"
|
|
127
126
|
- !ruby/object:Gem::Version
|
|
128
127
|
version: '1.1'
|
|
128
|
+
- !ruby/object:Gem::Dependency
|
|
129
|
+
name: redis
|
|
130
|
+
requirement: !ruby/object:Gem::Requirement
|
|
131
|
+
requirements:
|
|
132
|
+
- - ">="
|
|
133
|
+
- !ruby/object:Gem::Version
|
|
134
|
+
version: 4.0.1
|
|
135
|
+
type: :runtime
|
|
136
|
+
prerelease: false
|
|
137
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
138
|
+
requirements:
|
|
139
|
+
- - ">="
|
|
140
|
+
- !ruby/object:Gem::Version
|
|
141
|
+
version: 4.0.1
|
|
142
|
+
- !ruby/object:Gem::Dependency
|
|
143
|
+
name: turbo-rails
|
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
|
145
|
+
requirements:
|
|
146
|
+
- - ">="
|
|
147
|
+
- !ruby/object:Gem::Version
|
|
148
|
+
version: '1.0'
|
|
149
|
+
type: :runtime
|
|
150
|
+
prerelease: false
|
|
151
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
152
|
+
requirements:
|
|
153
|
+
- - ">="
|
|
154
|
+
- !ruby/object:Gem::Version
|
|
155
|
+
version: '1.0'
|
|
129
156
|
description: Studio Engine is a non-isolated Rails engine that ships an opinionated
|
|
130
157
|
authentication + SSO contract, a polymorphic ErrorLog model, a Sluggable concern,
|
|
131
158
|
a 7-role dynamic theme system with CSS-custom-property generation, and an S3-backed
|
|
@@ -169,11 +196,13 @@ files:
|
|
|
169
196
|
- app/mailers/application_mailer.rb
|
|
170
197
|
- app/mailers/user_mailer.rb
|
|
171
198
|
- app/models/concerns/sluggable.rb
|
|
199
|
+
- app/models/concerns/studio/broadcastable.rb
|
|
172
200
|
- app/models/current.rb
|
|
173
201
|
- app/models/error_log.rb
|
|
174
202
|
- app/models/image_cache.rb
|
|
175
203
|
- app/models/session_context.rb
|
|
176
204
|
- app/models/studio/email_delivery.rb
|
|
205
|
+
- app/models/studio/enumeral.rb
|
|
177
206
|
- app/models/studio/link.rb
|
|
178
207
|
- app/models/theme_setting.rb
|
|
179
208
|
- app/services/google_oauth_validator.rb
|
|
@@ -235,8 +264,10 @@ files:
|
|
|
235
264
|
- db/migrate/20260614000000_create_studio_email_deliveries.rb
|
|
236
265
|
- db/migrate/20260620000001_create_studio_links.rb
|
|
237
266
|
- db/migrate/20260620000002_allow_null_image_cache_owner.rb
|
|
267
|
+
- db/migrate/20260623130000_create_studio_enumerals.rb
|
|
238
268
|
- lib/studio-engine.rb
|
|
239
269
|
- lib/studio.rb
|
|
270
|
+
- lib/studio/cable.rb
|
|
240
271
|
- lib/studio/color_scale.rb
|
|
241
272
|
- lib/studio/email.rb
|
|
242
273
|
- lib/studio/email_smoke.rb
|
|
@@ -244,6 +275,7 @@ files:
|
|
|
244
275
|
- lib/studio/image_cache.rb
|
|
245
276
|
- lib/studio/link_token.rb
|
|
246
277
|
- lib/studio/mail_transport.rb
|
|
278
|
+
- lib/studio/redis.rb
|
|
247
279
|
- lib/studio/s3.rb
|
|
248
280
|
- lib/studio/theme_resolver.rb
|
|
249
281
|
- lib/studio/ui_primitives.rb
|
|
@@ -261,7 +293,6 @@ metadata:
|
|
|
261
293
|
source_code_uri: https://github.com/amcritchie/studio-engine/tree/main
|
|
262
294
|
bug_tracker_uri: https://github.com/amcritchie/studio-engine/issues
|
|
263
295
|
changelog_uri: https://github.com/amcritchie/studio-engine/blob/main/CHANGELOG.md
|
|
264
|
-
post_install_message:
|
|
265
296
|
rdoc_options: []
|
|
266
297
|
require_paths:
|
|
267
298
|
- lib
|
|
@@ -276,8 +307,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
276
307
|
- !ruby/object:Gem::Version
|
|
277
308
|
version: '0'
|
|
278
309
|
requirements: []
|
|
279
|
-
rubygems_version:
|
|
280
|
-
signing_key:
|
|
310
|
+
rubygems_version: 4.0.9
|
|
281
311
|
specification_version: 4
|
|
282
312
|
summary: Shared Rails engine providing auth, SSO, error logging, theming, and S3-backed
|
|
283
313
|
image caching
|