replace catalog service to api
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
.bundle/
|
||||
log/
|
||||
tmp/
|
||||
*.sqlite3
|
||||
.env
|
||||
@@ -0,0 +1,24 @@
|
||||
FROM --platform=linux/amd64 ruby:3.3-slim
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
default-libmysqlclient-dev \
|
||||
git \
|
||||
tzdata \
|
||||
unzip \
|
||||
nodejs \
|
||||
libffi-dev \
|
||||
libyaml-dev \
|
||||
pkg-config \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY Gemfile ./
|
||||
RUN bundle install
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["/bin/sh", "entrypoint.sh"]
|
||||
@@ -0,0 +1,16 @@
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "rails", "~> 8.0"
|
||||
gem "mysql2", "~> 0.5"
|
||||
gem "puma", ">= 5.0"
|
||||
gem "rubyzip", "~> 2.3"
|
||||
gem "bootsnap", require: false
|
||||
|
||||
gem "activeadmin"
|
||||
gem "devise"
|
||||
gem "sprockets-rails"
|
||||
gem "sassc-rails"
|
||||
|
||||
group :development, :test do
|
||||
gem "debug", platforms: %i[mri windows]
|
||||
end
|
||||
@@ -0,0 +1,298 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
action_text-trix (2.1.18)
|
||||
railties
|
||||
actioncable (8.1.3)
|
||||
actionpack (= 8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
zeitwerk (~> 2.6)
|
||||
actionmailbox (8.1.3)
|
||||
actionpack (= 8.1.3)
|
||||
activejob (= 8.1.3)
|
||||
activerecord (= 8.1.3)
|
||||
activestorage (= 8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
mail (>= 2.8.0)
|
||||
actionmailer (8.1.3)
|
||||
actionpack (= 8.1.3)
|
||||
actionview (= 8.1.3)
|
||||
activejob (= 8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
mail (>= 2.8.0)
|
||||
rails-dom-testing (~> 2.2)
|
||||
actionpack (8.1.3)
|
||||
actionview (= 8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
nokogiri (>= 1.8.5)
|
||||
rack (>= 2.2.4)
|
||||
rack-session (>= 1.0.1)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
useragent (~> 0.16)
|
||||
actiontext (8.1.3)
|
||||
action_text-trix (~> 2.1.15)
|
||||
actionpack (= 8.1.3)
|
||||
activerecord (= 8.1.3)
|
||||
activestorage (= 8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.11)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
activeadmin (3.5.1)
|
||||
arbre (~> 1.2, >= 1.2.1)
|
||||
csv
|
||||
formtastic (>= 3.1)
|
||||
formtastic_i18n (>= 0.4)
|
||||
inherited_resources (~> 1.7)
|
||||
jquery-rails (>= 4.2)
|
||||
kaminari (>= 1.2.1)
|
||||
railties (>= 6.1)
|
||||
ransack (>= 4.0)
|
||||
activejob (8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
activerecord (8.1.3)
|
||||
activemodel (= 8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
timeout (>= 0.4.0)
|
||||
activestorage (8.1.3)
|
||||
actionpack (= 8.1.3)
|
||||
activejob (= 8.1.3)
|
||||
activerecord (= 8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
marcel (~> 1.0)
|
||||
activesupport (8.1.3)
|
||||
base64
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||
connection_pool (>= 2.2.5)
|
||||
drb
|
||||
i18n (>= 1.6, < 2)
|
||||
json
|
||||
logger (>= 1.4.2)
|
||||
minitest (>= 5.1)
|
||||
securerandom (>= 0.3)
|
||||
tzinfo (~> 2.0, >= 2.0.5)
|
||||
uri (>= 0.13.1)
|
||||
arbre (1.7.0)
|
||||
activesupport (>= 3.0.0)
|
||||
ruby2_keywords (>= 0.0.2)
|
||||
base64 (0.3.0)
|
||||
bcrypt (3.1.22)
|
||||
bigdecimal (4.1.2)
|
||||
bootsnap (1.24.3)
|
||||
msgpack (~> 1.2)
|
||||
builder (3.3.0)
|
||||
concurrent-ruby (1.3.6)
|
||||
connection_pool (3.0.2)
|
||||
crass (1.0.6)
|
||||
csv (3.3.5)
|
||||
date (3.5.1)
|
||||
debug (1.11.1)
|
||||
irb (~> 1.10)
|
||||
reline (>= 0.3.8)
|
||||
devise (5.0.3)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 7.0)
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
drb (2.2.3)
|
||||
erb (6.0.4)
|
||||
erubi (1.13.1)
|
||||
ffi (1.17.4-x86_64-linux-gnu)
|
||||
formtastic (6.0.0)
|
||||
actionpack (>= 7.2.0)
|
||||
formtastic_i18n (0.7.0)
|
||||
globalid (1.3.0)
|
||||
activesupport (>= 6.1)
|
||||
has_scope (0.9.0)
|
||||
actionpack (>= 7.0)
|
||||
activesupport (>= 7.0)
|
||||
i18n (1.14.8)
|
||||
concurrent-ruby (~> 1.0)
|
||||
inherited_resources (1.14.0)
|
||||
actionpack (>= 6.0)
|
||||
has_scope (>= 0.6)
|
||||
railties (>= 6.0)
|
||||
responders (>= 2)
|
||||
io-console (0.8.2)
|
||||
irb (1.18.0)
|
||||
pp (>= 0.6.0)
|
||||
prism (>= 1.3.0)
|
||||
rdoc (>= 4.0.0)
|
||||
reline (>= 0.4.2)
|
||||
jquery-rails (4.6.1)
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
json (2.19.5)
|
||||
kaminari (1.2.2)
|
||||
activesupport (>= 4.1.0)
|
||||
kaminari-actionview (= 1.2.2)
|
||||
kaminari-activerecord (= 1.2.2)
|
||||
kaminari-core (= 1.2.2)
|
||||
kaminari-actionview (1.2.2)
|
||||
actionview
|
||||
kaminari-core (= 1.2.2)
|
||||
kaminari-activerecord (1.2.2)
|
||||
activerecord
|
||||
kaminari-core (= 1.2.2)
|
||||
kaminari-core (1.2.2)
|
||||
logger (1.7.0)
|
||||
loofah (2.25.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
mail (2.9.0)
|
||||
logger
|
||||
mini_mime (>= 0.1.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
marcel (1.1.0)
|
||||
mini_mime (1.1.5)
|
||||
minitest (6.0.6)
|
||||
drb (~> 2.0)
|
||||
prism (~> 1.5)
|
||||
msgpack (1.8.0)
|
||||
mysql2 (0.5.7)
|
||||
bigdecimal
|
||||
net-imap (0.6.4)
|
||||
date
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
net-protocol
|
||||
net-protocol (0.2.2)
|
||||
timeout
|
||||
net-smtp (0.5.1)
|
||||
net-protocol
|
||||
nio4r (2.7.5)
|
||||
nokogiri (1.19.3-x86_64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
orm_adapter (0.5.0)
|
||||
pp (0.6.3)
|
||||
prettyprint
|
||||
prettyprint (0.2.0)
|
||||
prism (1.9.0)
|
||||
psych (5.3.1)
|
||||
date
|
||||
stringio
|
||||
puma (8.0.1)
|
||||
nio4r (~> 2.0)
|
||||
racc (1.8.1)
|
||||
rack (3.2.6)
|
||||
rack-session (2.1.2)
|
||||
base64 (>= 0.1.0)
|
||||
rack (>= 3.0.0)
|
||||
rack-test (2.2.0)
|
||||
rack (>= 1.3)
|
||||
rackup (2.3.1)
|
||||
rack (>= 3)
|
||||
rails (8.1.3)
|
||||
actioncable (= 8.1.3)
|
||||
actionmailbox (= 8.1.3)
|
||||
actionmailer (= 8.1.3)
|
||||
actionpack (= 8.1.3)
|
||||
actiontext (= 8.1.3)
|
||||
actionview (= 8.1.3)
|
||||
activejob (= 8.1.3)
|
||||
activemodel (= 8.1.3)
|
||||
activerecord (= 8.1.3)
|
||||
activestorage (= 8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 8.1.3)
|
||||
rails-dom-testing (2.3.0)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.7.0)
|
||||
loofah (~> 2.25)
|
||||
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
||||
railties (8.1.3)
|
||||
actionpack (= 8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
irb (~> 1.13)
|
||||
rackup (>= 1.0.0)
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0, >= 1.2.2)
|
||||
tsort (>= 0.2)
|
||||
zeitwerk (~> 2.6)
|
||||
rake (13.4.2)
|
||||
ransack (4.4.1)
|
||||
activerecord (>= 7.2)
|
||||
activesupport (>= 7.2)
|
||||
i18n
|
||||
rdoc (7.2.0)
|
||||
erb
|
||||
psych (>= 4.0.0)
|
||||
tsort
|
||||
reline (0.6.3)
|
||||
io-console (~> 0.5)
|
||||
responders (3.2.0)
|
||||
actionpack (>= 7.0)
|
||||
railties (>= 7.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.4.1)
|
||||
sassc (2.4.0)
|
||||
ffi (~> 1.9)
|
||||
sassc-rails (2.1.2)
|
||||
railties (>= 4.0.0)
|
||||
sassc (>= 2.0)
|
||||
sprockets (> 3.0)
|
||||
sprockets-rails
|
||||
tilt
|
||||
securerandom (0.4.1)
|
||||
sprockets (4.2.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
logger
|
||||
rack (>= 2.2.4, < 4)
|
||||
sprockets-rails (3.5.2)
|
||||
actionpack (>= 6.1)
|
||||
activesupport (>= 6.1)
|
||||
sprockets (>= 3.0.0)
|
||||
stringio (3.2.0)
|
||||
thor (1.5.0)
|
||||
tilt (2.7.0)
|
||||
timeout (0.6.1)
|
||||
tsort (0.2.0)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
uri (1.1.1)
|
||||
useragent (0.16.11)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
websocket-driver (0.8.0)
|
||||
base64
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
zeitwerk (2.7.5)
|
||||
|
||||
PLATFORMS
|
||||
x86_64-linux-gnu
|
||||
|
||||
DEPENDENCIES
|
||||
activeadmin
|
||||
bootsnap
|
||||
debug
|
||||
devise
|
||||
mysql2 (~> 0.5)
|
||||
puma (>= 5.0)
|
||||
rails (~> 8.0)
|
||||
rubyzip (~> 2.3)
|
||||
sassc-rails
|
||||
sprockets-rails
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.22
|
||||
@@ -0,0 +1,2 @@
|
||||
require_relative "config/application"
|
||||
Rails.application.load_tasks
|
||||
@@ -0,0 +1,32 @@
|
||||
ActiveAdmin.register AdminUser do
|
||||
permit_params :email, :password, :password_confirmation
|
||||
|
||||
index do
|
||||
selectable_column
|
||||
id_column
|
||||
column :email
|
||||
column :created_at
|
||||
actions
|
||||
end
|
||||
|
||||
filter :email
|
||||
filter :created_at
|
||||
|
||||
form do |f|
|
||||
f.inputs do
|
||||
f.input :email
|
||||
f.input :password
|
||||
f.input :password_confirmation
|
||||
end
|
||||
f.actions
|
||||
end
|
||||
|
||||
show do
|
||||
attributes_table do
|
||||
row :id
|
||||
row :email
|
||||
row :created_at
|
||||
row :updated_at
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,27 @@
|
||||
ActiveAdmin.register_page "Dashboard" do
|
||||
menu priority: 1, label: proc { I18n.t("active_admin.dashboard") }
|
||||
|
||||
content title: proc { I18n.t("active_admin.dashboard") } do
|
||||
columns do
|
||||
column do
|
||||
panel "Softwares" do
|
||||
ul do
|
||||
li "Total: #{Software.count}"
|
||||
li "Released: #{Software.where(status: 'released').count}"
|
||||
li "In development: #{Software.where(status: 'development').count}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
column do
|
||||
panel "Recent Releases" do
|
||||
table_for Release.order(created_at: :desc).limit(10) do
|
||||
column(:software) { |r| link_to r.software.title, admin_software_path(r.software) rescue r.software_id }
|
||||
column :version
|
||||
column :created_at
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,34 @@
|
||||
ActiveAdmin.register ExternalLink do
|
||||
permit_params :software_id, :label, :url
|
||||
|
||||
menu false
|
||||
|
||||
belongs_to :software
|
||||
|
||||
index do
|
||||
selectable_column
|
||||
id_column
|
||||
column :label
|
||||
column(:url) { |el| link_to el.url, el.url, target: "_blank" }
|
||||
column :created_at
|
||||
actions
|
||||
end
|
||||
|
||||
form do |f|
|
||||
f.inputs do
|
||||
f.input :label
|
||||
f.input :url
|
||||
end
|
||||
f.actions
|
||||
end
|
||||
|
||||
show do
|
||||
attributes_table do
|
||||
row :id
|
||||
row :label
|
||||
row(:url) { |el| link_to el.url, el.url, target: "_blank" }
|
||||
row :created_at
|
||||
row :updated_at
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,33 @@
|
||||
ActiveAdmin.register Release do
|
||||
actions :index, :show
|
||||
|
||||
menu false
|
||||
|
||||
belongs_to :software, optional: true
|
||||
|
||||
index do
|
||||
selectable_column
|
||||
id_column
|
||||
column(:software) { |r| link_to r.software.title, admin_software_path(r.software) }
|
||||
column :version
|
||||
column :html_folder_path
|
||||
column :created_at
|
||||
actions only: [:show]
|
||||
end
|
||||
|
||||
filter :version
|
||||
|
||||
show do
|
||||
attributes_table do
|
||||
row :id
|
||||
row(:software) { |r| link_to r.software.title, admin_software_path(r.software) }
|
||||
row :version
|
||||
row :cartridge_path
|
||||
row :source_path
|
||||
row :html_folder_path
|
||||
row :docs_folder_path
|
||||
row :created_at
|
||||
row :updated_at
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,97 @@
|
||||
ActiveAdmin.register Software do
|
||||
permit_params :name, :title, :author, :desc, :story,
|
||||
:license, :platform, :status, :highlighted,
|
||||
external_links_attributes: [:id, :label, :url, :_destroy],
|
||||
releases_attributes: [:id, :_destroy]
|
||||
|
||||
menu priority: 2
|
||||
|
||||
index do
|
||||
selectable_column
|
||||
id_column
|
||||
column :name
|
||||
column :title
|
||||
column :platform
|
||||
column :status do |sw|
|
||||
status_tag sw.status
|
||||
end
|
||||
column :highlighted
|
||||
column :created_at
|
||||
actions
|
||||
end
|
||||
|
||||
filter :name
|
||||
filter :title
|
||||
filter :author
|
||||
filter :platform, as: :select, collection: %w[tic80 ebitengine love]
|
||||
filter :status, as: :select, collection: %w[development demo released archived]
|
||||
filter :highlighted
|
||||
|
||||
show do
|
||||
attributes_table do
|
||||
row :id
|
||||
row :name
|
||||
row :title
|
||||
row :author
|
||||
row :platform
|
||||
row :status
|
||||
row :highlighted
|
||||
row :license
|
||||
row(:desc) { |sw| simple_format sw.desc }
|
||||
row(:story) { |sw| simple_format sw.story }
|
||||
row :created_at
|
||||
row :updated_at
|
||||
end
|
||||
|
||||
panel "External Links" do
|
||||
table_for resource.external_links do
|
||||
column :label
|
||||
column(:url) { |el| link_to el.url, el.url, target: "_blank" }
|
||||
column :created_at
|
||||
column do |el|
|
||||
link_to "Edit", edit_admin_software_external_link_path(resource, el)
|
||||
concat " "
|
||||
link_to "Delete", admin_software_external_link_path(resource, el),
|
||||
method: :delete, data: { confirm: "Are you sure?" }
|
||||
end
|
||||
end
|
||||
br
|
||||
link_to "Add External Link",
|
||||
new_admin_software_external_link_path(resource)
|
||||
end
|
||||
|
||||
panel "Releases" do
|
||||
table_for resource.releases.order(created_at: :desc) do
|
||||
column :version
|
||||
column :html_folder_path
|
||||
column :cartridge_path
|
||||
column :source_path
|
||||
column :docs_folder_path
|
||||
column :created_at
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
form do |f|
|
||||
f.inputs "Details" do
|
||||
f.input :name
|
||||
f.input :title
|
||||
f.input :author
|
||||
f.input :platform, as: :select, collection: %w[tic80 ebitengine love]
|
||||
f.input :status, as: :select, collection: %w[development demo released archived]
|
||||
f.input :highlighted
|
||||
f.input :license
|
||||
f.input :desc, as: :text, input_html: { rows: 4 }
|
||||
f.input :story, as: :text, input_html: { rows: 8 }
|
||||
end
|
||||
|
||||
f.inputs "External Links" do
|
||||
f.has_many :external_links, allow_destroy: true, new_record: true do |el|
|
||||
el.input :label
|
||||
el.input :url
|
||||
end
|
||||
end
|
||||
|
||||
f.actions
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,2 @@
|
||||
//= link active_admin.css
|
||||
//= link active_admin.js
|
||||
@@ -0,0 +1 @@
|
||||
//= require active_admin/base
|
||||
@@ -0,0 +1,2 @@
|
||||
@import "active_admin/mixins";
|
||||
@import "active_admin/base";
|
||||
@@ -0,0 +1,8 @@
|
||||
module Api
|
||||
class SoftwareController < ApiController
|
||||
def index
|
||||
result = SoftwareListService.new.call
|
||||
render json: result
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,12 @@
|
||||
module Api
|
||||
class SoftwareHighlightedController < ApiController
|
||||
def index
|
||||
result = SoftwareHighlightedService.new.call
|
||||
if result.nil?
|
||||
render json: { error: "no highlighted software found" }, status: :not_found
|
||||
else
|
||||
render json: result
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,2 @@
|
||||
class ApiController < ActionController::API
|
||||
end
|
||||
@@ -0,0 +1,3 @@
|
||||
class ApplicationController < ActionController::Base
|
||||
protect_from_forgery with: :null_session
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
class FilesController < ApplicationController
|
||||
def show
|
||||
base = Pathname.new(ENV.fetch("FILE_CONTAINER_PATH", "/softwares")).realpath
|
||||
full = base.join(params[:path])
|
||||
|
||||
return render plain: "Not Found", status: :not_found unless full.to_s.start_with?(base.to_s)
|
||||
return render plain: "Not Found", status: :not_found unless full.exist? && full.file?
|
||||
|
||||
send_file full.to_s, disposition: "inline"
|
||||
rescue Errno::ENOENT
|
||||
render plain: "Not Found", status: :not_found
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,32 @@
|
||||
class UpdateController < ApiController
|
||||
PLATFORM_SERVICES = {
|
||||
"tic80" => "SoftwareUpdater::Tic80Service",
|
||||
"ebitengine" => "SoftwareUpdater::EbitengineService",
|
||||
"love" => "SoftwareUpdater::LoveService"
|
||||
}.freeze
|
||||
|
||||
def update
|
||||
return render plain: "Unauthorized", status: :unauthorized unless authorized?
|
||||
|
||||
platform = params[:platform]
|
||||
name = params[:name]
|
||||
version = params[:version]
|
||||
|
||||
return render plain: "Version not provided", status: :bad_request if version.blank?
|
||||
|
||||
service_class_name = PLATFORM_SERVICES[platform]
|
||||
return render plain: "Unsupported platform: #{platform}", status: :bad_request unless service_class_name
|
||||
|
||||
service_class_name.constantize.new.update(name, version)
|
||||
render plain: "Updated"
|
||||
rescue => e
|
||||
Rails.logger.error("[UpdateController] #{e.class}: #{e.message}\n#{e.backtrace.first(5).join("\n")}")
|
||||
render plain: e.message, status: :internal_server_error
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authorized?
|
||||
params[:secret] == ENV.fetch("UPDATE_SECRET", "")
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
class AdminUser < ApplicationRecord
|
||||
devise :database_authenticatable,
|
||||
:recoverable, :rememberable, :validatable
|
||||
|
||||
def self.ransackable_attributes(auth_object = nil)
|
||||
%w[created_at email id updated_at]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,3 @@
|
||||
class ApplicationRecord < ActiveRecord::Base
|
||||
primary_abstract_class
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
class ExternalLink < ApplicationRecord
|
||||
self.table_name = "external_links"
|
||||
|
||||
belongs_to :software
|
||||
|
||||
default_scope { where(deleted_at: nil) }
|
||||
|
||||
def self.ransackable_attributes(auth_object = nil)
|
||||
%w[created_at deleted_at id label software_id updated_at url]
|
||||
end
|
||||
|
||||
def self.ransackable_associations(auth_object = nil)
|
||||
%w[software]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
class Release < ApplicationRecord
|
||||
self.table_name = "releases"
|
||||
|
||||
belongs_to :software
|
||||
|
||||
default_scope { where(deleted_at: nil) }
|
||||
|
||||
def self.ransackable_attributes(auth_object = nil)
|
||||
%w[cartridge_path created_at deleted_at docs_folder_path html_folder_path id software_id source_path updated_at version web_playable]
|
||||
end
|
||||
|
||||
def self.ransackable_associations(auth_object = nil)
|
||||
%w[software]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
class Software < ApplicationRecord
|
||||
self.table_name = "softwares"
|
||||
|
||||
has_many :releases, foreign_key: :software_id
|
||||
has_many :external_links, foreign_key: :software_id
|
||||
|
||||
accepts_nested_attributes_for :external_links, allow_destroy: true
|
||||
|
||||
default_scope { where(deleted_at: nil) }
|
||||
|
||||
def self.ransackable_attributes(auth_object = nil)
|
||||
%w[author created_at desc highlighted id license name platform site status story title updated_at]
|
||||
end
|
||||
|
||||
def self.ransackable_associations(auth_object = nil)
|
||||
%w[releases external_links]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,12 @@
|
||||
class SoftwareHighlightedService
|
||||
def call
|
||||
software = Software.includes(:external_links)
|
||||
.where(highlighted: true)
|
||||
.order(id: :desc)
|
||||
.first
|
||||
return nil unless software
|
||||
|
||||
releases = Release.where(software_id: software.id).to_a
|
||||
SoftwareSerializer.show_data(software, releases)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,9 @@
|
||||
class SoftwareListService
|
||||
def call
|
||||
softwares = Software.includes(:releases, :external_links).all
|
||||
items = softwares.map do |sw|
|
||||
SoftwareSerializer.show_data(sw, sw.releases.to_a)
|
||||
end
|
||||
{ softwares: items }
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,82 @@
|
||||
class SoftwareSerializer
|
||||
GO_ZERO_TIME = "0001-01-01T00:00:00Z"
|
||||
FILE_PATH_FROM = "/softwares/"
|
||||
FILE_PATH_TO = "/file/"
|
||||
|
||||
def self.show_data(software, releases)
|
||||
sorted = releases.sort_by { |r| r.created_at || Time.at(0) }.reverse
|
||||
|
||||
latest = sorted.reject { |r| r.version.to_s.start_with?("dev-") }.first
|
||||
web_playable = sorted.find { |r| r.html_folder_path.present? }
|
||||
|
||||
{
|
||||
software: serialize_software(software),
|
||||
releases: sorted.map { |r| serialize_release(r) },
|
||||
latestRelease: latest ? serialize_release(latest) : nil,
|
||||
webPlayableRelease: web_playable ? serialize_release(web_playable) : nil
|
||||
}
|
||||
end
|
||||
|
||||
def self.serialize_software(sw)
|
||||
{
|
||||
"ID" => sw.id,
|
||||
"CreatedAt" => fmt(sw.created_at),
|
||||
"UpdatedAt" => fmt(sw.updated_at),
|
||||
"DeletedAt" => fmt_nullable(sw.deleted_at),
|
||||
"name" => sw.name,
|
||||
"title" => sw.title,
|
||||
"author" => sw.author,
|
||||
"desc" => sw.desc.to_s,
|
||||
"story" => sw.story.to_s,
|
||||
"license" => sw.license.to_s,
|
||||
"platform" => sw.platform,
|
||||
"status" => sw.status,
|
||||
"highlighted" => sw.highlighted ? true : false,
|
||||
"externalLinks" => sw.external_links.map { |el| serialize_external_link(el) }
|
||||
}
|
||||
end
|
||||
|
||||
def self.serialize_release(r)
|
||||
{
|
||||
"ID" => r.id,
|
||||
"CreatedAt" => fmt(r.created_at),
|
||||
"UpdatedAt" => fmt(r.updated_at),
|
||||
"DeletedAt" => fmt_nullable(r.deleted_at),
|
||||
"softwareId" => r.software_id,
|
||||
"version" => r.version,
|
||||
"cartridgePath" => rewrite(r.cartridge_path),
|
||||
"sourcePath" => rewrite(r.source_path),
|
||||
"htmlFolderPath" => rewrite(r.html_folder_path),
|
||||
"docsFolderPath" => rewrite(r.docs_folder_path)
|
||||
}
|
||||
end
|
||||
|
||||
def self.serialize_external_link(el)
|
||||
{
|
||||
"ID" => el.id,
|
||||
"CreatedAt" => fmt(el.created_at),
|
||||
"UpdatedAt" => fmt(el.updated_at),
|
||||
"DeletedAt" => fmt_nullable(el.deleted_at),
|
||||
"softwareId" => el.software_id,
|
||||
"label" => el.label,
|
||||
"url" => el.url
|
||||
}
|
||||
end
|
||||
|
||||
# null → Go zero-time string (matches GORM behavior)
|
||||
def self.fmt(t)
|
||||
return GO_ZERO_TIME if t.nil?
|
||||
t.utc.strftime("%Y-%m-%dT%H:%M:%S.%3NZ")
|
||||
end
|
||||
|
||||
# null → JSON null (matches gorm.DeletedAt behavior)
|
||||
def self.fmt_nullable(t)
|
||||
return nil if t.nil?
|
||||
t.utc.strftime("%Y-%m-%dT%H:%M:%S.%3NZ")
|
||||
end
|
||||
|
||||
def self.rewrite(path)
|
||||
return "" if path.blank?
|
||||
path.gsub(FILE_PATH_FROM, FILE_PATH_TO)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,11 @@
|
||||
class SoftwareShowService
|
||||
def initialize(name)
|
||||
@name = name
|
||||
end
|
||||
|
||||
def call
|
||||
software = Software.includes(:external_links).find_by!(name: @name)
|
||||
releases = Release.where(software_id: software.id).to_a
|
||||
SoftwareSerializer.show_data(software, releases)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,61 @@
|
||||
require "zip"
|
||||
require "fileutils"
|
||||
|
||||
module SoftwareUpdater
|
||||
class BaseService
|
||||
def initialize
|
||||
@base_path = ENV.fetch("FILE_CONTAINER_PATH", "/softwares")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def full_path(filename)
|
||||
File.join(@base_path, filename)
|
||||
end
|
||||
|
||||
def dir_exists?(dirname)
|
||||
File.directory?(full_path(dirname))
|
||||
end
|
||||
|
||||
def delete_dir(dirname)
|
||||
FileUtils.rm_rf(full_path(dirname))
|
||||
end
|
||||
|
||||
def create_dir(dirname)
|
||||
FileUtils.mkdir_p(full_path(dirname))
|
||||
end
|
||||
|
||||
def unzip_file(zip_filename, dest_dirname)
|
||||
dest = full_path(dest_dirname)
|
||||
Zip::File.open(full_path(zip_filename)) do |zip|
|
||||
zip.each do |entry|
|
||||
entry_dest = File.join(dest, entry.name)
|
||||
FileUtils.mkdir_p(File.dirname(entry_dest))
|
||||
entry.extract(entry_dest) { true }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_or_create_software(attrs)
|
||||
software = Software.unscoped.find_or_initialize_by(name: attrs[:name])
|
||||
software.assign_attributes(attrs.except(:name))
|
||||
software.deleted_at = nil
|
||||
software.save!
|
||||
software
|
||||
end
|
||||
|
||||
def upsert_external_link(software_id, label, url)
|
||||
link = ExternalLink.unscoped.find_or_initialize_by(software_id: software_id, label: label)
|
||||
link.url = url
|
||||
link.deleted_at = nil
|
||||
link.save!
|
||||
end
|
||||
|
||||
def create_release_if_not_exists(attrs)
|
||||
existing = Release.unscoped.find_by(software_id: attrs[:software_id], version: attrs[:version])
|
||||
return existing if existing
|
||||
|
||||
Release.create!(attrs)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,34 @@
|
||||
require "json"
|
||||
|
||||
module SoftwareUpdater
|
||||
class EbitengineService < BaseService
|
||||
def update(name, version)
|
||||
versioned = "#{name}-#{version}"
|
||||
zip_file = "#{versioned}.html.zip"
|
||||
html_dir = versioned
|
||||
|
||||
delete_dir(html_dir) if dir_exists?(html_dir)
|
||||
create_dir(html_dir)
|
||||
unzip_file(zip_file, html_dir)
|
||||
|
||||
metadata = parse_metadata(full_path("#{versioned}.metadata.json"))
|
||||
site_url = metadata.delete(:site)
|
||||
|
||||
software = update_or_create_software(metadata.merge(platform: "ebitengine"))
|
||||
upsert_external_link(software.id, "Source Code", site_url) if site_url.present?
|
||||
|
||||
create_release_if_not_exists(
|
||||
software_id: software.id,
|
||||
version: version,
|
||||
html_folder_path: full_path(html_dir)
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_metadata(path)
|
||||
raw = JSON.parse(File.read(path), symbolize_names: true)
|
||||
raw.slice(:name, :title, :author, :desc, :site, :license)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,34 @@
|
||||
require "json"
|
||||
|
||||
module SoftwareUpdater
|
||||
class LoveService < BaseService
|
||||
def update(name, version)
|
||||
versioned = "#{name}-#{version}"
|
||||
zip_file = "#{versioned}.html.zip"
|
||||
html_dir = versioned
|
||||
|
||||
delete_dir(html_dir) if dir_exists?(html_dir)
|
||||
create_dir(html_dir)
|
||||
unzip_file(zip_file, html_dir)
|
||||
|
||||
metadata = parse_metadata(full_path("#{versioned}.metadata.json"))
|
||||
site_url = metadata.delete(:site)
|
||||
|
||||
software = update_or_create_software(metadata.merge(platform: "love"))
|
||||
upsert_external_link(software.id, "Source Code", site_url) if site_url.present?
|
||||
|
||||
create_release_if_not_exists(
|
||||
software_id: software.id,
|
||||
version: version,
|
||||
html_folder_path: full_path(html_dir)
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_metadata(path)
|
||||
raw = JSON.parse(File.read(path), symbolize_names: true)
|
||||
raw.slice(:name, :title, :author, :desc, :site, :license)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,50 @@
|
||||
module SoftwareUpdater
|
||||
class Tic80Service < BaseService
|
||||
def update(name, version)
|
||||
versioned = "#{name}-#{version}"
|
||||
zip_file = "#{versioned}.html.zip"
|
||||
docs_zip_file = "#{versioned}-docs.zip"
|
||||
html_dir = versioned
|
||||
docs_dir = "#{versioned}-docs"
|
||||
|
||||
delete_dir(html_dir) if dir_exists?(html_dir)
|
||||
delete_dir(docs_dir) if dir_exists?(docs_dir)
|
||||
|
||||
create_dir(html_dir)
|
||||
unzip_file(zip_file, html_dir)
|
||||
|
||||
create_dir(docs_dir)
|
||||
unzip_file(docs_zip_file, docs_dir)
|
||||
|
||||
metadata = parse_lua_metadata(full_path("#{versioned}.lua"))
|
||||
site_url = metadata.delete(:site)
|
||||
|
||||
software = update_or_create_software(metadata.merge(platform: "tic80"))
|
||||
upsert_external_link(software.id, "Source Code", site_url) if site_url.present?
|
||||
|
||||
create_release_if_not_exists(
|
||||
software_id: software.id,
|
||||
version: version,
|
||||
cartridge_path: full_path("#{versioned}.tic"),
|
||||
source_path: full_path("#{versioned}.lua"),
|
||||
html_folder_path: full_path(html_dir),
|
||||
docs_folder_path: full_path(docs_dir)
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_lua_metadata(source_path)
|
||||
metadata = {}
|
||||
File.foreach(source_path) do |line|
|
||||
break unless line.start_with?("--")
|
||||
parts = line[2..].split(":", 2)
|
||||
next if parts.length != 2
|
||||
key = parts[0].strip.downcase.to_sym
|
||||
value = parts[1].strip
|
||||
metadata[key] = value
|
||||
end
|
||||
metadata
|
||||
end
|
||||
end
|
||||
end
|
||||
Executable
+81
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
#
|
||||
# This file was generated by Bundler.
|
||||
#
|
||||
# The application's executable is in bin/bundle.
|
||||
#
|
||||
|
||||
require "rubygems"
|
||||
|
||||
m = Module.new do
|
||||
module_function
|
||||
|
||||
def invoked_as_script?
|
||||
File.expand_path($0) == File.expand_path(__FILE__)
|
||||
end
|
||||
|
||||
def env_var_version
|
||||
ENV["BUNDLER_VERSION"]
|
||||
end
|
||||
|
||||
def cli_arg_version
|
||||
return unless invoked_as_script?
|
||||
return unless "--version" == ARGV.first
|
||||
bundler_version = nil
|
||||
update_index = nil
|
||||
ARGV.each_with_index do |a, i|
|
||||
bundler_version = a if update_index && update_index.succ == i
|
||||
next unless a =~ /\A--bundler\z/
|
||||
update_index = i
|
||||
end
|
||||
bundler_version
|
||||
end
|
||||
|
||||
def gemfile
|
||||
gemfile = ENV["BUNDLE_GEMFILE"]
|
||||
return gemfile if gemfile && !gemfile.empty?
|
||||
File.expand_path("../Gemfile", __dir__)
|
||||
end
|
||||
|
||||
def lockfile
|
||||
lockfile = "#{gemfile}.lock"
|
||||
return lockfile if File.exist?(lockfile)
|
||||
nil
|
||||
end
|
||||
|
||||
def bundler_version
|
||||
@bundler_version ||=
|
||||
env_var_version || cli_arg_version ||
|
||||
lockfile_version
|
||||
end
|
||||
|
||||
def lockfile_version
|
||||
return unless lockfile
|
||||
lockfile_contents = File.read(lockfile)
|
||||
return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s*(#{Gem::Version::VERSION_PATTERN})\n/
|
||||
Regexp.last_match(1)
|
||||
end
|
||||
|
||||
def load_bundler!
|
||||
$LOAD_PATH.unshift(bundler_spec.lib_dirs_glob) if bundler_spec
|
||||
require "bundler"
|
||||
unless bundler_spec
|
||||
Gem::GemRunner.new.run(["install", "bundler", "--version", bundler_version]) if bundler_version
|
||||
require "bundler"
|
||||
end
|
||||
end
|
||||
|
||||
def bundler_spec
|
||||
@bundler_spec ||= Gem::Specification.find_by_name("bundler", bundler_version)
|
||||
rescue Gem::MissingSpecVersionError, Gem::MissingSpecError
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
m.load_bundler!
|
||||
|
||||
if m.invoked_as_script?
|
||||
load Gem.bin_path("bundler", "bundle")
|
||||
end
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env ruby
|
||||
APP_PATH = File.expand_path("../config/application", __dir__)
|
||||
require_relative "../config/boot"
|
||||
require "rails/commands"
|
||||
@@ -0,0 +1,3 @@
|
||||
require_relative "config/environment"
|
||||
run Rails.application
|
||||
Rails.application.load_server
|
||||
@@ -0,0 +1,23 @@
|
||||
require_relative "boot"
|
||||
require "rails"
|
||||
require "active_model/railtie"
|
||||
require "active_record/railtie"
|
||||
require "action_controller/railtie"
|
||||
require "action_view/railtie"
|
||||
require "action_dispatch/railtie"
|
||||
require "sprockets/railtie"
|
||||
|
||||
Bundler.require(*Rails.groups)
|
||||
|
||||
module Api
|
||||
class Application < Rails::Application
|
||||
config.load_defaults 8.0
|
||||
|
||||
config.time_zone = "UTC"
|
||||
config.active_record.default_timezone = :utc
|
||||
|
||||
config.assets.prefix = "/admin/assets"
|
||||
|
||||
config.autoload_lib(ignore: %w[assets tasks])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,4 @@
|
||||
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
|
||||
|
||||
require 'bundler/setup'
|
||||
require 'bootsnap/setup'
|
||||
@@ -0,0 +1,16 @@
|
||||
default: &default
|
||||
adapter: mysql2
|
||||
encoding: utf8mb4
|
||||
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
||||
username: <%= ENV.fetch("DB_USER", "root") %>
|
||||
password: <%= ENV.fetch("DB_PASSWORD", "") %>
|
||||
host: <%= ENV.fetch("DB_HOST", "mysql") %>
|
||||
port: <%= ENV.fetch("DB_PORT", "3306") %>
|
||||
|
||||
development:
|
||||
<<: *default
|
||||
database: <%= ENV.fetch("DB_NAME", "softwares") %>
|
||||
|
||||
production:
|
||||
<<: *default
|
||||
database: <%= ENV.fetch("DB_NAME", "softwares") %>
|
||||
@@ -0,0 +1,3 @@
|
||||
require_relative "application"
|
||||
|
||||
Rails.application.initialize!
|
||||
@@ -0,0 +1,14 @@
|
||||
Rails.application.configure do
|
||||
config.eager_load = false
|
||||
config.consider_all_requests_local = true
|
||||
config.server_timing = true
|
||||
|
||||
config.cache_classes = false
|
||||
config.action_controller.perform_caching = false
|
||||
|
||||
config.active_record.migration_error = :page_load
|
||||
config.active_record.verbose_query_logs = true
|
||||
|
||||
config.log_level = :debug
|
||||
config.log_tags = [:request_id]
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
Rails.application.configure do
|
||||
config.eager_load = true
|
||||
config.consider_all_requests_local = false
|
||||
config.log_level = :info
|
||||
config.log_tags = [:request_id]
|
||||
config.active_record.verbose_query_logs = false
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
ActiveAdmin.setup do |config|
|
||||
config.site_title = "Teletype Games Admin"
|
||||
|
||||
config.authentication_method = :authenticate_admin_user!
|
||||
config.current_user_method = :current_admin_user
|
||||
config.logout_link_path = :destroy_admin_user_session_path
|
||||
config.logout_link_method = :delete
|
||||
|
||||
config.comments = false
|
||||
config.batch_actions = true
|
||||
config.filter_attributes = [:password, :password_confirmation, :current_password, :encrypted_password]
|
||||
|
||||
config.localize_format = :long
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
Devise.setup do |config|
|
||||
config.mailer_sender = "noreply@teletype.hu"
|
||||
|
||||
require "devise/orm/active_record"
|
||||
|
||||
config.case_insensitive_keys = [:email]
|
||||
config.strip_whitespace_keys = [:email]
|
||||
config.skip_session_storage = [:http_auth]
|
||||
config.stretches = Rails.env.test? ? 1 : 12
|
||||
config.reconfirmable = false
|
||||
config.expire_all_remember_me_on_sign_out = true
|
||||
config.password_length = 6..128
|
||||
config.email_regexp = /\A[^@\s]+@[^@\s]+\z/
|
||||
config.reset_password_within = 6.hours
|
||||
config.sign_out_via = :delete
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
Rails.application.config.action_dispatch.default_headers = {
|
||||
"Cross-Origin-Opener-Policy" => "same-origin",
|
||||
"Cross-Origin-Embedder-Policy" => "require-corp",
|
||||
"Cross-Origin-Resource-Policy" => "same-origin"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
max_threads_count = ENV.fetch("RAILS_MAX_THREADS", 5)
|
||||
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
|
||||
threads min_threads_count, max_threads_count
|
||||
|
||||
port ENV.fetch("PORT", 3000)
|
||||
environment ENV.fetch("RAILS_ENV", "development")
|
||||
pidfile ENV.fetch("PIDFILE", "tmp/pids/server.pid")
|
||||
plugin :tmp_restart
|
||||
@@ -0,0 +1,12 @@
|
||||
Rails.application.routes.draw do
|
||||
devise_for :admin_users, ActiveAdmin::Devise.config
|
||||
ActiveAdmin.routes(self)
|
||||
|
||||
namespace :api do
|
||||
get "software", to: "software#index"
|
||||
get "software/highlighted", to: "software_highlighted#index"
|
||||
end
|
||||
|
||||
get "update", to: "update#update"
|
||||
get "file/*path", to: "files#show", format: false
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
class CreateSoftwares < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
create_table :softwares, id: { type: :bigint, unsigned: true },
|
||||
charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci",
|
||||
if_not_exists: true do |t|
|
||||
t.string :author
|
||||
t.datetime :created_at, precision: 3
|
||||
t.datetime :deleted_at, precision: 3
|
||||
t.text :desc
|
||||
t.boolean :highlighted, default: false
|
||||
t.string :license, limit: 128
|
||||
t.string :name, limit: 128
|
||||
t.string :platform, limit: 128
|
||||
t.string :site
|
||||
t.string :status, limit: 20, default: "development"
|
||||
t.text :story
|
||||
t.string :title
|
||||
t.datetime :updated_at, precision: 3
|
||||
|
||||
t.index :deleted_at, name: "idx_softwares_deleted_at"
|
||||
t.index :name, name: "idx_softwares_name", unique: true
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,23 @@
|
||||
class CreateReleases < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
create_table :releases, id: { type: :bigint, unsigned: true },
|
||||
charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci",
|
||||
if_not_exists: true do |t|
|
||||
t.string :cartridge_path
|
||||
t.datetime :created_at, precision: 3
|
||||
t.datetime :deleted_at, precision: 3
|
||||
t.string :docs_folder_path
|
||||
t.string :html_folder_path
|
||||
t.bigint :software_id, unsigned: true
|
||||
t.string :source_path
|
||||
t.datetime :updated_at, precision: 3
|
||||
t.string :version, limit: 64
|
||||
t.boolean :web_playable, default: false
|
||||
|
||||
t.index :deleted_at, name: "idx_releases_deleted_at"
|
||||
t.index :software_id, name: "idx_releases_software_id"
|
||||
end
|
||||
|
||||
add_foreign_key :releases, :softwares, name: "fk_softwares_releases" unless foreign_key_exists?(:releases, :softwares)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
class CreateExternalLinks < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
create_table :external_links, id: { type: :bigint, unsigned: true },
|
||||
charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci",
|
||||
if_not_exists: true do |t|
|
||||
t.datetime :created_at, precision: 3
|
||||
t.datetime :deleted_at, precision: 3
|
||||
t.string :label, limit: 128
|
||||
t.bigint :software_id, unsigned: true
|
||||
t.datetime :updated_at, precision: 3
|
||||
t.string :url
|
||||
|
||||
t.index :deleted_at, name: "idx_external_links_deleted_at"
|
||||
t.index :software_id, name: "idx_external_links_software_id"
|
||||
end
|
||||
|
||||
add_foreign_key :external_links, :softwares, name: "fk_softwares_external_links" unless foreign_key_exists?(:external_links, :softwares)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
class CreateAdminUsers < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
create_table :admin_users, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci",
|
||||
if_not_exists: true do |t|
|
||||
t.string :email, null: false, default: ""
|
||||
t.string :encrypted_password, null: false, default: ""
|
||||
t.string :reset_password_token
|
||||
t.datetime :reset_password_sent_at
|
||||
t.datetime :remember_created_at
|
||||
t.datetime :created_at, null: false
|
||||
t.datetime :updated_at, null: false
|
||||
|
||||
t.index :email, name: "index_admin_users_on_email", unique: true
|
||||
t.index :reset_password_token, name: "index_admin_users_on_reset_password_token", unique: true
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,173 @@
|
||||
# This file is auto-generated from the current state of the database. Instead
|
||||
# of editing this file, please use the migrations feature of Active Record to
|
||||
# incrementally modify your database, and then regenerate this schema definition.
|
||||
#
|
||||
# This file is the source Rails uses to define your schema when running `bin/rails
|
||||
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
||||
# be faster and is potentially less error prone than running all of your
|
||||
# migrations from scratch. Old migrations may fail to apply correctly if those
|
||||
# migrations use external dependencies or application code.
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.1].define(version: 2026_05_05_000004) do
|
||||
create_table "admin_users", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.string "email", default: "", null: false
|
||||
t.string "encrypted_password", default: "", null: false
|
||||
t.datetime "remember_created_at"
|
||||
t.datetime "reset_password_sent_at"
|
||||
t.string "reset_password_token"
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["email"], name: "index_admin_users_on_email", unique: true
|
||||
t.index ["reset_password_token"], name: "index_admin_users_on_reset_password_token", unique: true
|
||||
end
|
||||
|
||||
create_table "articles", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||
t.text "content"
|
||||
t.timestamp "created_at", default: -> { "CURRENT_TIMESTAMP" }
|
||||
t.string "slug"
|
||||
t.string "title"
|
||||
t.timestamp "updated_at", default: -> { "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" }
|
||||
end
|
||||
|
||||
create_table "external_links", id: { type: :bigint, unsigned: true }, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||
t.datetime "created_at", precision: 3
|
||||
t.datetime "deleted_at", precision: 3
|
||||
t.string "label", limit: 128
|
||||
t.bigint "software_id", unsigned: true
|
||||
t.datetime "updated_at", precision: 3
|
||||
t.string "url"
|
||||
t.index ["deleted_at"], name: "idx_external_links_deleted_at"
|
||||
t.index ["software_id"], name: "idx_external_links_software_id"
|
||||
end
|
||||
|
||||
create_table "goadmin_menu", id: { type: :integer, unsigned: true }, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||
t.timestamp "created_at", default: -> { "CURRENT_TIMESTAMP" }
|
||||
t.string "header", limit: 150
|
||||
t.string "icon", limit: 50, null: false
|
||||
t.integer "order", default: 0, null: false
|
||||
t.integer "parent_id", default: 0, null: false
|
||||
t.string "plugin_name", limit: 150, default: "", null: false
|
||||
t.string "title", limit: 50, null: false
|
||||
t.integer "type", default: 0, null: false
|
||||
t.timestamp "updated_at", default: -> { "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" }
|
||||
t.string "uri", null: false
|
||||
t.string "uuid", limit: 150
|
||||
end
|
||||
|
||||
create_table "goadmin_operation_log", id: { type: :integer, unsigned: true }, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||
t.timestamp "created_at", default: -> { "CURRENT_TIMESTAMP" }
|
||||
t.text "input", null: false
|
||||
t.string "ip", limit: 15, null: false
|
||||
t.string "method", limit: 10, null: false
|
||||
t.string "path", null: false
|
||||
t.timestamp "updated_at", default: -> { "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" }
|
||||
t.integer "user_id", null: false
|
||||
end
|
||||
|
||||
create_table "goadmin_permissions", id: { type: :integer, unsigned: true }, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||
t.timestamp "created_at", default: -> { "CURRENT_TIMESTAMP" }
|
||||
t.string "http_method"
|
||||
t.text "http_path"
|
||||
t.string "name", limit: 50, null: false
|
||||
t.string "slug", limit: 50, null: false
|
||||
t.timestamp "updated_at", default: -> { "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" }
|
||||
t.index ["name"], name: "permissions_name_unique", unique: true
|
||||
t.index ["slug"], name: "permissions_slug_unique", unique: true
|
||||
end
|
||||
|
||||
create_table "goadmin_role_menu", id: false, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||
t.timestamp "created_at", default: -> { "CURRENT_TIMESTAMP" }
|
||||
t.integer "menu_id", null: false
|
||||
t.integer "role_id", null: false
|
||||
t.timestamp "updated_at", default: -> { "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" }
|
||||
end
|
||||
|
||||
create_table "goadmin_role_permissions", id: false, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||
t.timestamp "created_at", default: -> { "CURRENT_TIMESTAMP" }
|
||||
t.integer "permission_id", null: false
|
||||
t.integer "role_id", null: false
|
||||
t.timestamp "updated_at", default: -> { "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" }
|
||||
end
|
||||
|
||||
create_table "goadmin_role_users", id: false, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||
t.timestamp "created_at", default: -> { "CURRENT_TIMESTAMP" }
|
||||
t.integer "role_id", null: false
|
||||
t.timestamp "updated_at", default: -> { "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" }
|
||||
t.integer "user_id", null: false
|
||||
end
|
||||
|
||||
create_table "goadmin_roles", id: { type: :integer, unsigned: true }, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||
t.timestamp "created_at", default: -> { "CURRENT_TIMESTAMP" }
|
||||
t.string "name", limit: 100, null: false
|
||||
t.string "slug", limit: 100, null: false
|
||||
t.timestamp "updated_at", default: -> { "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" }
|
||||
t.index ["name"], name: "roles_name_unique", unique: true
|
||||
t.index ["slug"], name: "roles_slug_unique", unique: true
|
||||
end
|
||||
|
||||
create_table "goadmin_site", id: { type: :integer, unsigned: true }, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||
t.timestamp "created_at", default: -> { "CURRENT_TIMESTAMP" }
|
||||
t.string "description", limit: 3000, null: false
|
||||
t.string "key_", limit: 100, null: false
|
||||
t.integer "state", limit: 1, default: 0, null: false, unsigned: true
|
||||
t.timestamp "updated_at", default: -> { "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" }
|
||||
t.text "value", null: false
|
||||
t.index ["key_"], name: "site_key_unique", unique: true
|
||||
end
|
||||
|
||||
create_table "goadmin_user_permissions", id: false, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||
t.timestamp "created_at", default: -> { "CURRENT_TIMESTAMP" }
|
||||
t.integer "permission_id", null: false
|
||||
t.timestamp "updated_at", default: -> { "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" }
|
||||
t.integer "user_id", null: false
|
||||
end
|
||||
|
||||
create_table "goadmin_users", id: { type: :integer, unsigned: true }, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||
t.string "avatar"
|
||||
t.timestamp "created_at", default: -> { "CURRENT_TIMESTAMP" }
|
||||
t.string "name", limit: 100, null: false
|
||||
t.string "password", limit: 100, null: false
|
||||
t.string "remember_token", limit: 100
|
||||
t.timestamp "updated_at", default: -> { "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" }
|
||||
t.string "username", limit: 100, null: false
|
||||
t.index ["username"], name: "users_username_unique", unique: true
|
||||
end
|
||||
|
||||
create_table "releases", id: { type: :bigint, unsigned: true }, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||
t.string "cartridge_path"
|
||||
t.datetime "created_at", precision: 3
|
||||
t.datetime "deleted_at", precision: 3
|
||||
t.string "docs_folder_path"
|
||||
t.string "html_folder_path"
|
||||
t.bigint "software_id", unsigned: true
|
||||
t.string "source_path"
|
||||
t.datetime "updated_at", precision: 3
|
||||
t.string "version", limit: 64
|
||||
t.boolean "web_playable", default: false
|
||||
t.index ["deleted_at"], name: "idx_releases_deleted_at"
|
||||
t.index ["software_id"], name: "idx_releases_software_id"
|
||||
end
|
||||
|
||||
create_table "softwares", id: { type: :bigint, unsigned: true }, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||
t.string "author"
|
||||
t.datetime "created_at", precision: 3
|
||||
t.datetime "deleted_at", precision: 3
|
||||
t.text "desc"
|
||||
t.boolean "highlighted", default: false
|
||||
t.string "license", limit: 128
|
||||
t.string "name", limit: 128
|
||||
t.string "platform", limit: 128
|
||||
t.string "site"
|
||||
t.string "status", limit: 20, default: "development"
|
||||
t.text "story"
|
||||
t.string "title"
|
||||
t.datetime "updated_at", precision: 3
|
||||
t.index ["deleted_at"], name: "idx_softwares_deleted_at"
|
||||
t.index ["name"], name: "idx_softwares_name", unique: true
|
||||
end
|
||||
|
||||
add_foreign_key "external_links", "softwares", name: "fk_softwares_external_links"
|
||||
add_foreign_key "releases", "softwares", name: "fk_softwares_releases"
|
||||
end
|
||||
@@ -0,0 +1,4 @@
|
||||
AdminUser.find_or_create_by!(email: ENV.fetch("ADMIN_EMAIL", "admin@teletype.hu")) do |user|
|
||||
user.password = ENV.fetch("ADMIN_PASSWORD", "password123")
|
||||
user.password_confirmation = ENV.fetch("ADMIN_PASSWORD", "password123")
|
||||
end
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
mkdir -p tmp/pids tmp/cache tmp/sockets log
|
||||
rm -f tmp/pids/server.pid
|
||||
|
||||
bundle check || bundle install
|
||||
|
||||
bundle exec rails db:migrate
|
||||
bundle exec rails db:seed
|
||||
|
||||
bundle exec rails server -b 0.0.0.0 -p 3000
|
||||
Reference in New Issue
Block a user