Store resource

This commit is contained in:
2026-08-18 13:04:21 +02:00
parent c85148d238
commit 4dccb9a815
12 changed files with 226 additions and 1 deletions
+53
View File
@@ -0,0 +1,53 @@
ActiveAdmin.register Store do
permit_params :name, :catalog_url, :store_repository_url
menu priority: 5, label: "🛒 Stores"
index do
selectable_column
id_column
column :name
column :catalog_url do |store|
link_to store.catalog_url, store.catalog_url, target: "_blank", rel: "noopener"
end
column :store_repository_url do |store|
link_to store.store_repository_url, store.store_repository_url, target: "_blank", rel: "noopener"
end
column :updated_at
actions
end
filter :name
filter :catalog_url
filter :store_repository_url
show do
attributes_table do
row :id
row :name
row :catalog_url do |store|
link_to store.catalog_url, store.catalog_url, target: "_blank", rel: "noopener"
end
row :store_repository_url do |store|
link_to store.store_repository_url, store.store_repository_url, target: "_blank", rel: "noopener"
end
row :created_at
row :updated_at
end
para do
"Listed by GET /api/stores, which the desktop client reads on first run: it " \
"takes the config from the store repository and points it at the catalog above."
end
end
form do |f|
f.inputs do
f.input :name, hint: "What the client shows in its store picker"
f.input :catalog_url, hint: "Base URL of the WarpEngine catalog, e.g. https://teletypegames.org"
f.input :store_repository_url,
hint: "Repository holding the store's config.json, e.g. " \
"https://git.teletypegames.org/stores/ttg-desktop-store"
end
f.actions
end
end
@@ -0,0 +1,20 @@
class Api::StoresController < ApiController
resource_description do
short "Stores"
end
api :GET, "/api/stores", "List the stores a client can install from"
desc <<~DESC
The registry the graphical desktop client reads on first run: which catalogs
exist, and where each one's store configuration lives. Public on purpose —
a client has nobody to log in as.
DESC
returns code: 200, desc: "Array of stores" do
property :name, String, desc: "Display name of the store"
property :catalogUrl, String, desc: "Base URL of the WarpEngine catalog it serves"
property :storeRepositoryUrl, String, desc: "Repository holding the store's config.json"
end
def index
render json: StoreService.new.index
end
end
+21
View File
@@ -0,0 +1,21 @@
# A store a client can install from: a WarpEngine catalog, and the repository
# holding the store's own configuration.
#
# This is deliberately not part of WarpEngine. The engine serves one catalog and
# has no business knowing which stores exist for it; the registry is a property of
# this site, which is what the graphical client asks.
class Store < ApplicationRecord
URL = %r{\Ahttps?://\S+\z}
validates :name, presence: true
validates :catalog_url, presence: true, format: { with: URL, message: "must be an http(s) URL" }
validates :store_repository_url, presence: true, format: { with: URL, message: "must be an http(s) URL" }
default_scope { where(deleted_at: nil) }
scope :ordered, -> { order(:name) }
def self.ransackable_attributes(auth_object = nil)
%w[id name catalog_url store_repository_url created_at updated_at]
end
end
@@ -0,0 +1,9 @@
class StoreSerializer < Blueprinter::Base
include WarpEngine::TimestampFields
field :name
# camelCase, as the catalog's own payloads use — one convention for a client
# that reads both.
field(:catalogUrl) { |store| store.catalog_url }
field(:storeRepositoryUrl) { |store| store.store_repository_url }
end
+5
View File
@@ -0,0 +1,5 @@
class StoreService
def index
StoreSerializer.render_as_hash(Store.ordered)
end
end