better_structure_sql 0.1.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 +7 -0
- data/CHANGELOG.md +41 -0
- data/LICENSE +21 -0
- data/README.md +557 -0
- data/app/controllers/better_structure_sql/application_controller.rb +61 -0
- data/app/controllers/better_structure_sql/schema_versions_controller.rb +243 -0
- data/app/helpers/better_structure_sql/schema_versions_helper.rb +46 -0
- data/app/views/better_structure_sql/schema_versions/index.html.erb +110 -0
- data/app/views/better_structure_sql/schema_versions/show.html.erb +186 -0
- data/app/views/layouts/better_structure_sql/application.html.erb +105 -0
- data/config/database.yml +3 -0
- data/config/routes.rb +12 -0
- data/lib/better_structure_sql/adapters/base_adapter.rb +234 -0
- data/lib/better_structure_sql/adapters/mysql_adapter.rb +476 -0
- data/lib/better_structure_sql/adapters/mysql_config.rb +32 -0
- data/lib/better_structure_sql/adapters/postgresql_adapter.rb +646 -0
- data/lib/better_structure_sql/adapters/postgresql_config.rb +25 -0
- data/lib/better_structure_sql/adapters/registry.rb +115 -0
- data/lib/better_structure_sql/adapters/sqlite_adapter.rb +644 -0
- data/lib/better_structure_sql/adapters/sqlite_config.rb +26 -0
- data/lib/better_structure_sql/configuration.rb +129 -0
- data/lib/better_structure_sql/database_version.rb +46 -0
- data/lib/better_structure_sql/dependency_resolver.rb +63 -0
- data/lib/better_structure_sql/dumper.rb +544 -0
- data/lib/better_structure_sql/engine.rb +28 -0
- data/lib/better_structure_sql/file_writer.rb +180 -0
- data/lib/better_structure_sql/formatter.rb +70 -0
- data/lib/better_structure_sql/generators/base.rb +33 -0
- data/lib/better_structure_sql/generators/domain_generator.rb +22 -0
- data/lib/better_structure_sql/generators/extension_generator.rb +23 -0
- data/lib/better_structure_sql/generators/foreign_key_generator.rb +43 -0
- data/lib/better_structure_sql/generators/function_generator.rb +33 -0
- data/lib/better_structure_sql/generators/index_generator.rb +50 -0
- data/lib/better_structure_sql/generators/materialized_view_generator.rb +31 -0
- data/lib/better_structure_sql/generators/pragma_generator.rb +23 -0
- data/lib/better_structure_sql/generators/sequence_generator.rb +27 -0
- data/lib/better_structure_sql/generators/table_generator.rb +126 -0
- data/lib/better_structure_sql/generators/trigger_generator.rb +54 -0
- data/lib/better_structure_sql/generators/type_generator.rb +47 -0
- data/lib/better_structure_sql/generators/view_generator.rb +27 -0
- data/lib/better_structure_sql/introspection/extensions.rb +29 -0
- data/lib/better_structure_sql/introspection/foreign_keys.rb +29 -0
- data/lib/better_structure_sql/introspection/functions.rb +29 -0
- data/lib/better_structure_sql/introspection/indexes.rb +29 -0
- data/lib/better_structure_sql/introspection/sequences.rb +29 -0
- data/lib/better_structure_sql/introspection/tables.rb +29 -0
- data/lib/better_structure_sql/introspection/triggers.rb +29 -0
- data/lib/better_structure_sql/introspection/types.rb +37 -0
- data/lib/better_structure_sql/introspection/views.rb +41 -0
- data/lib/better_structure_sql/introspection.rb +31 -0
- data/lib/better_structure_sql/manifest_generator.rb +65 -0
- data/lib/better_structure_sql/migration_patch.rb +196 -0
- data/lib/better_structure_sql/pg_version.rb +44 -0
- data/lib/better_structure_sql/railtie.rb +124 -0
- data/lib/better_structure_sql/schema_loader.rb +168 -0
- data/lib/better_structure_sql/schema_version.rb +86 -0
- data/lib/better_structure_sql/schema_versions.rb +213 -0
- data/lib/better_structure_sql/version.rb +5 -0
- data/lib/better_structure_sql/zip_generator.rb +81 -0
- data/lib/better_structure_sql.rb +81 -0
- data/lib/generators/better_structure_sql/install_generator.rb +44 -0
- data/lib/generators/better_structure_sql/migration_generator.rb +34 -0
- data/lib/generators/better_structure_sql/templates/README +49 -0
- data/lib/generators/better_structure_sql/templates/add_metadata_migration.rb.erb +25 -0
- data/lib/generators/better_structure_sql/templates/better_structure_sql.rb +46 -0
- data/lib/generators/better_structure_sql/templates/migration.rb.erb +26 -0
- data/lib/tasks/better_structure_sql.rake +190 -0
- metadata +299 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterStructureSql
|
|
4
|
+
# Controller for browsing and downloading stored schema versions
|
|
5
|
+
#
|
|
6
|
+
# Provides web UI actions for listing, viewing, and downloading schema
|
|
7
|
+
# versions stored in the database. Implements memory-efficient streaming
|
|
8
|
+
# for large files and multi-file ZIP archive support.
|
|
9
|
+
#
|
|
10
|
+
# @see SchemaVersion
|
|
11
|
+
class SchemaVersionsController < ApplicationController
|
|
12
|
+
# Maximum file size to load into memory (2MB)
|
|
13
|
+
MAX_MEMORY_SIZE = 2.megabytes
|
|
14
|
+
# Maximum file size to display in browser (200KB)
|
|
15
|
+
MAX_DISPLAY_SIZE = 200.kilobytes
|
|
16
|
+
|
|
17
|
+
# Lists stored schema versions with pagination
|
|
18
|
+
#
|
|
19
|
+
# Loads only metadata (no content or zip_archive) for performance.
|
|
20
|
+
# Displays up to 100 most recent versions ordered by creation date.
|
|
21
|
+
#
|
|
22
|
+
# @return [void]
|
|
23
|
+
# GET /better_structure_sql/schema_versions
|
|
24
|
+
def index
|
|
25
|
+
# Load only metadata for listing (no content or zip_archive)
|
|
26
|
+
@schema_versions = SchemaVersion
|
|
27
|
+
.select(:id, :pg_version, :format_type, :output_mode, :created_at,
|
|
28
|
+
:content_size, :file_count)
|
|
29
|
+
.order(created_at: :desc)
|
|
30
|
+
.limit(100)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Displays details of a specific schema version
|
|
34
|
+
#
|
|
35
|
+
# Loads metadata first for performance. For small single-file versions
|
|
36
|
+
# (under 200KB), loads content for inline display. For multi-file versions,
|
|
37
|
+
# extracts and parses the embedded manifest JSON.
|
|
38
|
+
#
|
|
39
|
+
# @return [void]
|
|
40
|
+
# @raise [ActiveRecord::RecordNotFound] if schema version not found
|
|
41
|
+
# GET /better_structure_sql/schema_versions/:id
|
|
42
|
+
def show
|
|
43
|
+
# Load metadata first
|
|
44
|
+
@schema_version = SchemaVersion
|
|
45
|
+
.select(:id, :pg_version, :format_type, :output_mode, :created_at,
|
|
46
|
+
:content_size, :line_count, :file_count)
|
|
47
|
+
.find(params[:id])
|
|
48
|
+
|
|
49
|
+
# Only load content for small single-file versions
|
|
50
|
+
if @schema_version.output_mode == 'single_file' && @schema_version.content_size <= MAX_DISPLAY_SIZE
|
|
51
|
+
@schema_version = SchemaVersion.find(params[:id]) # Load with content
|
|
52
|
+
elsif @schema_version.output_mode == 'multi_file'
|
|
53
|
+
# Load content to extract manifest
|
|
54
|
+
full_version = SchemaVersion.select(:id, :content).find(params[:id])
|
|
55
|
+
@manifest = extract_manifest_from_content(full_version.content)
|
|
56
|
+
end
|
|
57
|
+
rescue ActiveRecord::RecordNotFound
|
|
58
|
+
render plain: 'Schema version not found', status: :not_found
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Downloads raw content of a schema version as plain text
|
|
62
|
+
#
|
|
63
|
+
# Streams large files (>2MB) in chunks to avoid memory issues.
|
|
64
|
+
# Smaller files are sent directly using send_data.
|
|
65
|
+
#
|
|
66
|
+
# @return [void]
|
|
67
|
+
# @raise [ActiveRecord::RecordNotFound] if schema version not found
|
|
68
|
+
# GET /better_structure_sql/schema_versions/:id/raw
|
|
69
|
+
def raw
|
|
70
|
+
version = SchemaVersion.select(:id, :format_type, :content_size).find(params[:id])
|
|
71
|
+
|
|
72
|
+
filename = "schema_version_#{version.id}_#{version.format_type}.txt"
|
|
73
|
+
|
|
74
|
+
# For large files, stream from database to avoid loading into memory
|
|
75
|
+
if version.content_size > MAX_MEMORY_SIZE
|
|
76
|
+
stream_large_file(version.id, filename)
|
|
77
|
+
else
|
|
78
|
+
# For smaller files, use regular send_data
|
|
79
|
+
content = SchemaVersion.find(version.id).content
|
|
80
|
+
send_data content,
|
|
81
|
+
filename: filename,
|
|
82
|
+
type: 'text/plain',
|
|
83
|
+
disposition: 'attachment'
|
|
84
|
+
end
|
|
85
|
+
rescue ActiveRecord::RecordNotFound
|
|
86
|
+
render plain: 'Schema version not found', status: :not_found
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Downloads schema version in appropriate format
|
|
90
|
+
#
|
|
91
|
+
# Multi-file versions with ZIP archives are sent as .zip files.
|
|
92
|
+
# Single-file versions are sent as .sql or .rb files based on format_type.
|
|
93
|
+
#
|
|
94
|
+
# @return [void]
|
|
95
|
+
# @raise [ActiveRecord::RecordNotFound] if schema version not found
|
|
96
|
+
# GET /better_structure_sql/schema_versions/:id/download
|
|
97
|
+
def download
|
|
98
|
+
version = SchemaVersion.find(params[:id])
|
|
99
|
+
|
|
100
|
+
if version.multi_file? && version.zip_archive?
|
|
101
|
+
send_zip_download(version)
|
|
102
|
+
else
|
|
103
|
+
send_file_download(version)
|
|
104
|
+
end
|
|
105
|
+
rescue ActiveRecord::RecordNotFound
|
|
106
|
+
render plain: 'Schema version not found', status: :not_found
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
# Sends ZIP archive download for multi-file schema versions
|
|
112
|
+
#
|
|
113
|
+
# Validates ZIP archive before sending to prevent corrupted downloads.
|
|
114
|
+
# Filename includes version ID and timestamp.
|
|
115
|
+
#
|
|
116
|
+
# @param version [SchemaVersion] the schema version to download
|
|
117
|
+
# @return [void]
|
|
118
|
+
def send_zip_download(version)
|
|
119
|
+
# Validate ZIP
|
|
120
|
+
BetterStructureSql::ZipGenerator.validate_zip!(version.zip_archive)
|
|
121
|
+
|
|
122
|
+
filename = "schema_version_#{version.id}_#{version.created_at.to_i}.zip"
|
|
123
|
+
|
|
124
|
+
send_data version.zip_archive,
|
|
125
|
+
filename: filename,
|
|
126
|
+
type: 'application/zip',
|
|
127
|
+
disposition: 'attachment'
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Sends single-file schema version download
|
|
131
|
+
#
|
|
132
|
+
# Streams large files (>2MB) to avoid memory issues. Filename is
|
|
133
|
+
# structure.sql or structure.rb based on format_type.
|
|
134
|
+
#
|
|
135
|
+
# @param version [SchemaVersion] the schema version to download
|
|
136
|
+
# @return [void]
|
|
137
|
+
def send_file_download(version)
|
|
138
|
+
extension = version.format_type == 'rb' ? 'rb' : 'sql'
|
|
139
|
+
filename = "structure.#{extension}"
|
|
140
|
+
|
|
141
|
+
# Handle large files with streaming
|
|
142
|
+
if version.content_size > MAX_MEMORY_SIZE
|
|
143
|
+
stream_large_content(version, filename)
|
|
144
|
+
else
|
|
145
|
+
send_data version.content,
|
|
146
|
+
filename: filename,
|
|
147
|
+
type: 'text/plain',
|
|
148
|
+
disposition: 'attachment'
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Streams large content in 64KB chunks to avoid memory issues
|
|
153
|
+
#
|
|
154
|
+
# Sets response headers for streaming and disables proxy buffering.
|
|
155
|
+
# Fetches content from database and yields chunks via Enumerator.
|
|
156
|
+
#
|
|
157
|
+
# @param version [SchemaVersion] the schema version to stream
|
|
158
|
+
# @param filename [String] the filename for Content-Disposition header
|
|
159
|
+
# @return [void]
|
|
160
|
+
def stream_large_content(version, filename)
|
|
161
|
+
response.headers['Content-Type'] = 'text/plain'
|
|
162
|
+
response.headers['Content-Disposition'] = "attachment; filename=\"#{filename}\""
|
|
163
|
+
response.headers['X-Accel-Buffering'] = 'no'
|
|
164
|
+
|
|
165
|
+
self.response_body = Enumerator.new do |yielder|
|
|
166
|
+
content = SchemaVersion.connection.select_value(
|
|
167
|
+
"SELECT content FROM #{SchemaVersion.table_name} WHERE id = #{version.id}"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
chunk_size = 64.kilobytes
|
|
171
|
+
offset = 0
|
|
172
|
+
while offset < content.bytesize
|
|
173
|
+
yielder << content.byteslice(offset, chunk_size)
|
|
174
|
+
offset += chunk_size
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Extracts embedded manifest JSON from multi-file schema content
|
|
180
|
+
#
|
|
181
|
+
# Manifest is stored between MANIFEST_JSON_START and MANIFEST_JSON_END markers
|
|
182
|
+
# as SQL comments. Parses and returns the manifest hash.
|
|
183
|
+
#
|
|
184
|
+
# @param content [String] the schema content containing embedded manifest
|
|
185
|
+
# @return [Hash, nil] parsed manifest hash or nil if not found/invalid
|
|
186
|
+
def extract_manifest_from_content(content)
|
|
187
|
+
# Manifest is embedded in content between MANIFEST_JSON_START and MANIFEST_JSON_END markers
|
|
188
|
+
return nil unless content.include?('MANIFEST_JSON_START')
|
|
189
|
+
|
|
190
|
+
# Extract JSON from between markers, removing comment prefixes
|
|
191
|
+
start_marker = '-- MANIFEST_JSON_START'
|
|
192
|
+
end_marker = '-- MANIFEST_JSON_END'
|
|
193
|
+
|
|
194
|
+
start_pos = content.index(start_marker)
|
|
195
|
+
end_pos = content.index(end_marker)
|
|
196
|
+
|
|
197
|
+
return nil unless start_pos && end_pos
|
|
198
|
+
|
|
199
|
+
manifest_section = content[(start_pos + start_marker.length)..(end_pos - 1)]
|
|
200
|
+
manifest_json = manifest_section.lines
|
|
201
|
+
.map { |line| line.sub(/^--\s?/, '') }
|
|
202
|
+
.join
|
|
203
|
+
|
|
204
|
+
JSON.parse(manifest_json)
|
|
205
|
+
rescue JSON::ParserError => e
|
|
206
|
+
Rails.logger.debug { "Failed to parse manifest: #{e.message}" }
|
|
207
|
+
nil
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Streams large file content from database in chunks
|
|
211
|
+
#
|
|
212
|
+
# Sets appropriate headers for streaming downloads and disables proxy buffering.
|
|
213
|
+
# Fetches content from database and streams in 64KB chunks via Enumerator.
|
|
214
|
+
#
|
|
215
|
+
# @param version_id [Integer] the schema version ID
|
|
216
|
+
# @param filename [String] the filename for Content-Disposition header
|
|
217
|
+
# @return [void]
|
|
218
|
+
def stream_large_file(version_id, filename)
|
|
219
|
+
# Set headers for streaming
|
|
220
|
+
response.headers['Content-Type'] = 'text/plain'
|
|
221
|
+
response.headers['Content-Disposition'] = "attachment; filename=\"#{filename}\""
|
|
222
|
+
response.headers['Cache-Control'] = 'no-cache'
|
|
223
|
+
response.headers['X-Accel-Buffering'] = 'no' # Disable proxy buffering
|
|
224
|
+
|
|
225
|
+
# Stream the content in chunks
|
|
226
|
+
self.response_body = Enumerator.new do |yielder|
|
|
227
|
+
# Fetch content in a streaming fashion from database
|
|
228
|
+
SchemaVersion.connection.select_value(
|
|
229
|
+
"SELECT content FROM #{SchemaVersion.table_name} WHERE id = #{version_id}"
|
|
230
|
+
).tap do |content|
|
|
231
|
+
# Stream in 64KB chunks
|
|
232
|
+
chunk_size = 64.kilobytes
|
|
233
|
+
offset = 0
|
|
234
|
+
|
|
235
|
+
while offset < content.bytesize
|
|
236
|
+
yielder << content.byteslice(offset, chunk_size)
|
|
237
|
+
offset += chunk_size
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterStructureSql
|
|
4
|
+
# View helper methods for schema versions display
|
|
5
|
+
#
|
|
6
|
+
# Provides formatting methods for rendering schema version attributes
|
|
7
|
+
# in the web UI, including badges and icons for output modes and format types.
|
|
8
|
+
module SchemaVersionsHelper
|
|
9
|
+
# Formats output mode as a Bootstrap badge with icon
|
|
10
|
+
#
|
|
11
|
+
# @param mode [String] the output mode ('multi_file' or 'single_file')
|
|
12
|
+
# @return [String] HTML-safe badge element with icon
|
|
13
|
+
def format_output_mode(mode)
|
|
14
|
+
case mode
|
|
15
|
+
when 'multi_file'
|
|
16
|
+
content_tag(:span, class: 'badge bg-info') do
|
|
17
|
+
concat content_tag(:i, '', class: 'bi bi-folder')
|
|
18
|
+
concat ' Multi-File'
|
|
19
|
+
end
|
|
20
|
+
when 'single_file'
|
|
21
|
+
content_tag(:span, class: 'badge bg-secondary') do
|
|
22
|
+
concat content_tag(:i, '', class: 'bi bi-file-earmark')
|
|
23
|
+
concat ' Single File'
|
|
24
|
+
end
|
|
25
|
+
else
|
|
26
|
+
content_tag(:span, 'Unknown', class: 'badge bg-warning')
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Formats format type as a Bootstrap badge with icon
|
|
31
|
+
#
|
|
32
|
+
# SQL format uses blue badge with SQL icon, Ruby format uses green
|
|
33
|
+
# badge with Ruby icon.
|
|
34
|
+
#
|
|
35
|
+
# @param format_type [String] the format type ('sql' or 'rb')
|
|
36
|
+
# @return [String] HTML-safe badge element with icon
|
|
37
|
+
def format_type_badge(format_type)
|
|
38
|
+
bg_class = format_type == 'sql' ? 'bg-primary' : 'bg-success'
|
|
39
|
+
icon_class = format_type == 'sql' ? 'bi-filetype-sql' : 'bi-filetype-rb'
|
|
40
|
+
content_tag(:span, class: "badge #{bg_class}") do
|
|
41
|
+
concat content_tag(:i, '', class: "bi #{icon_class}")
|
|
42
|
+
concat " #{format_type.upcase}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<div class="row">
|
|
2
|
+
<div class="col-12">
|
|
3
|
+
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
4
|
+
<h1 class="h2">
|
|
5
|
+
<i class="bi bi-database-fill-gear text-primary"></i>
|
|
6
|
+
Schema Versions
|
|
7
|
+
</h1>
|
|
8
|
+
<div class="text-muted">
|
|
9
|
+
<i class="bi bi-info-circle"></i>
|
|
10
|
+
Total: <%= @schema_versions.count %>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<% if @schema_versions.empty? %>
|
|
15
|
+
<div class="empty-state">
|
|
16
|
+
<i class="bi bi-inbox" style="font-size: 4rem;"></i>
|
|
17
|
+
<h3 class="mt-3">No Schema Versions Yet</h3>
|
|
18
|
+
<p class="text-muted">
|
|
19
|
+
Schema versions will appear here after running:<br>
|
|
20
|
+
<code>rails db:schema:dump</code> (with versioning enabled)
|
|
21
|
+
</p>
|
|
22
|
+
</div>
|
|
23
|
+
<% else %>
|
|
24
|
+
<div class="card shadow-sm">
|
|
25
|
+
<div class="table-responsive">
|
|
26
|
+
<table class="table table-hover table-striped mb-0">
|
|
27
|
+
<thead class="table-light">
|
|
28
|
+
<tr>
|
|
29
|
+
<th scope="col" class="text-center" style="width: 80px;">ID</th>
|
|
30
|
+
<th scope="col" style="width: 120px;">Format</th>
|
|
31
|
+
<th scope="col" style="width: 150px;">Mode</th>
|
|
32
|
+
<th scope="col" style="width: 180px;">PostgreSQL</th>
|
|
33
|
+
<th scope="col">Created At</th>
|
|
34
|
+
<th scope="col" style="width: 100px;">Files</th>
|
|
35
|
+
<th scope="col" style="width: 120px;">Size</th>
|
|
36
|
+
<th scope="col" class="text-end" style="width: 200px;">Actions</th>
|
|
37
|
+
</tr>
|
|
38
|
+
</thead>
|
|
39
|
+
<tbody>
|
|
40
|
+
<% @schema_versions.each do |version| %>
|
|
41
|
+
<tr>
|
|
42
|
+
<td class="text-center fw-bold text-muted">
|
|
43
|
+
#<%= version.id %>
|
|
44
|
+
</td>
|
|
45
|
+
<td>
|
|
46
|
+
<%= format_type_badge(version.format_type) %>
|
|
47
|
+
</td>
|
|
48
|
+
<td>
|
|
49
|
+
<%= format_output_mode(version.output_mode) %>
|
|
50
|
+
</td>
|
|
51
|
+
<td>
|
|
52
|
+
<span class="text-muted">
|
|
53
|
+
<i class="bi bi-server"></i>
|
|
54
|
+
<%= version.pg_version %>
|
|
55
|
+
</span>
|
|
56
|
+
</td>
|
|
57
|
+
<td>
|
|
58
|
+
<i class="bi bi-calendar-event"></i>
|
|
59
|
+
<%= version.created_at.strftime('%Y-%m-%d %H:%M:%S') %>
|
|
60
|
+
<small class="text-muted d-block">
|
|
61
|
+
<%= time_ago_in_words(version.created_at) %> ago
|
|
62
|
+
</small>
|
|
63
|
+
</td>
|
|
64
|
+
<td>
|
|
65
|
+
<% if version.file_count %>
|
|
66
|
+
<span class="badge bg-light text-dark">
|
|
67
|
+
<%= version.file_count %> files
|
|
68
|
+
</span>
|
|
69
|
+
<% else %>
|
|
70
|
+
<span class="text-muted">1 file</span>
|
|
71
|
+
<% end %>
|
|
72
|
+
</td>
|
|
73
|
+
<td>
|
|
74
|
+
<span class="badge bg-secondary">
|
|
75
|
+
<%= version.formatted_size %>
|
|
76
|
+
</span>
|
|
77
|
+
</td>
|
|
78
|
+
<td class="text-end">
|
|
79
|
+
<div class="btn-group btn-group-sm" role="group">
|
|
80
|
+
<a href="<%= better_structure_sql.schema_version_path(version) %>"
|
|
81
|
+
class="btn btn-outline-primary"
|
|
82
|
+
title="View details">
|
|
83
|
+
<i class="bi bi-eye"></i>
|
|
84
|
+
View
|
|
85
|
+
</a>
|
|
86
|
+
<a href="<%= better_structure_sql.download_schema_version_path(version) %>"
|
|
87
|
+
class="btn btn-outline-success"
|
|
88
|
+
title="Download"
|
|
89
|
+
data-turbo="false">
|
|
90
|
+
<i class="bi bi-download"></i>
|
|
91
|
+
Download
|
|
92
|
+
</a>
|
|
93
|
+
</div>
|
|
94
|
+
</td>
|
|
95
|
+
</tr>
|
|
96
|
+
<% end %>
|
|
97
|
+
</tbody>
|
|
98
|
+
</table>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<% if @schema_versions.count >= 100 %>
|
|
103
|
+
<div class="alert alert-info mt-3">
|
|
104
|
+
<i class="bi bi-info-circle"></i>
|
|
105
|
+
Showing the 100 most recent versions. Older versions are still stored in the database.
|
|
106
|
+
</div>
|
|
107
|
+
<% end %>
|
|
108
|
+
<% end %>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
<div class="row">
|
|
2
|
+
<div class="col-12">
|
|
3
|
+
<!-- Header with Back Button -->
|
|
4
|
+
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
5
|
+
<h1 class="h2">
|
|
6
|
+
<i class="bi bi-file-earmark-code text-primary"></i>
|
|
7
|
+
Schema Version #<%= @schema_version.id %>
|
|
8
|
+
</h1>
|
|
9
|
+
<a href="<%= better_structure_sql.schema_versions_path %>" class="btn btn-outline-secondary">
|
|
10
|
+
<i class="bi bi-arrow-left"></i>
|
|
11
|
+
Back to List
|
|
12
|
+
</a>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<!-- Metadata Card -->
|
|
16
|
+
<div class="card metadata-card shadow-sm mb-4">
|
|
17
|
+
<div class="card-body">
|
|
18
|
+
<div class="row">
|
|
19
|
+
<div class="col-md-3">
|
|
20
|
+
<h6 class="text-muted mb-1">
|
|
21
|
+
<i class="bi bi-hash"></i>
|
|
22
|
+
Version ID
|
|
23
|
+
</h6>
|
|
24
|
+
<p class="mb-0 fw-bold">#<%= @schema_version.id %></p>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="col-md-3">
|
|
27
|
+
<h6 class="text-muted mb-1">
|
|
28
|
+
<i class="bi bi-filetype-sql"></i>
|
|
29
|
+
Format
|
|
30
|
+
</h6>
|
|
31
|
+
<p class="mb-0">
|
|
32
|
+
<%= format_type_badge(@schema_version.format_type) %>
|
|
33
|
+
</p>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="col-md-3">
|
|
36
|
+
<h6 class="text-muted mb-1">
|
|
37
|
+
<i class="bi bi-folder"></i>
|
|
38
|
+
Output Mode
|
|
39
|
+
</h6>
|
|
40
|
+
<p class="mb-0">
|
|
41
|
+
<%= format_output_mode(@schema_version.output_mode) %>
|
|
42
|
+
</p>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="col-md-3">
|
|
45
|
+
<h6 class="text-muted mb-1">
|
|
46
|
+
<i class="bi bi-server"></i>
|
|
47
|
+
PostgreSQL Version
|
|
48
|
+
</h6>
|
|
49
|
+
<p class="mb-0"><%= @schema_version.pg_version %></p>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div class="row mt-3">
|
|
54
|
+
<div class="col-md-3">
|
|
55
|
+
<h6 class="text-muted mb-1">
|
|
56
|
+
<i class="bi bi-calendar-event"></i>
|
|
57
|
+
Created
|
|
58
|
+
</h6>
|
|
59
|
+
<p class="mb-0">
|
|
60
|
+
<%= @schema_version.created_at.strftime('%Y-%m-%d %H:%M:%S') %>
|
|
61
|
+
<small class="text-muted d-block">
|
|
62
|
+
<%= time_ago_in_words(@schema_version.created_at) %> ago
|
|
63
|
+
</small>
|
|
64
|
+
</p>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="col-md-3">
|
|
67
|
+
<h6 class="text-muted mb-1">
|
|
68
|
+
<i class="bi bi-file-earmark-bar-graph"></i>
|
|
69
|
+
Size
|
|
70
|
+
</h6>
|
|
71
|
+
<p class="mb-0">
|
|
72
|
+
<span class="badge bg-secondary"><%= @schema_version.formatted_size %></span>
|
|
73
|
+
</p>
|
|
74
|
+
</div>
|
|
75
|
+
<div class="col-md-3">
|
|
76
|
+
<h6 class="text-muted mb-1">
|
|
77
|
+
<i class="bi bi-info-circle"></i>
|
|
78
|
+
Lines
|
|
79
|
+
</h6>
|
|
80
|
+
<p class="mb-0"><%= number_with_delimiter(@schema_version.line_count) %> lines</p>
|
|
81
|
+
</div>
|
|
82
|
+
<div class="col-md-3">
|
|
83
|
+
<h6 class="text-muted mb-1">
|
|
84
|
+
<i class="bi bi-files"></i>
|
|
85
|
+
Files
|
|
86
|
+
</h6>
|
|
87
|
+
<p class="mb-0">
|
|
88
|
+
<% if @schema_version.file_count %>
|
|
89
|
+
<span class="badge bg-light text-dark"><%= @schema_version.file_count %> files</span>
|
|
90
|
+
<% else %>
|
|
91
|
+
<span class="text-muted">1 file</span>
|
|
92
|
+
<% end %>
|
|
93
|
+
</p>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<!-- Actions -->
|
|
100
|
+
<div class="mb-3">
|
|
101
|
+
<a href="<%= better_structure_sql.download_schema_version_path(@schema_version) %>"
|
|
102
|
+
class="btn btn-primary btn-lg"
|
|
103
|
+
data-turbo="false">
|
|
104
|
+
<% if @schema_version.multi_file? %>
|
|
105
|
+
<i class="bi bi-file-zip"></i>
|
|
106
|
+
Download ZIP Archive
|
|
107
|
+
<% else %>
|
|
108
|
+
<i class="bi bi-download"></i>
|
|
109
|
+
Download File
|
|
110
|
+
<% end %>
|
|
111
|
+
</a>
|
|
112
|
+
<% if @schema_version.respond_to?(:content) && @schema_version.content.present? %>
|
|
113
|
+
<button type="button" class="btn btn-outline-secondary btn-lg" onclick="copyToClipboard()">
|
|
114
|
+
<i class="bi bi-clipboard"></i>
|
|
115
|
+
Copy to Clipboard
|
|
116
|
+
</button>
|
|
117
|
+
<% end %>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<!-- Multi-file Info -->
|
|
121
|
+
<% if @schema_version.multi_file? && @manifest %>
|
|
122
|
+
<div class="alert alert-info mb-4">
|
|
123
|
+
<h6 class="alert-heading">
|
|
124
|
+
<i class="bi bi-info-circle"></i>
|
|
125
|
+
Multi-File Schema
|
|
126
|
+
</h6>
|
|
127
|
+
<p class="mb-2">This schema was generated in multi-file format with <%= @schema_version.file_count %> files across organized directories.</p>
|
|
128
|
+
<% if @manifest['directories'] %>
|
|
129
|
+
<p class="mb-0 small">
|
|
130
|
+
<strong>Directories:</strong>
|
|
131
|
+
<%= @manifest['directories'].keys.sort.join(', ') %>
|
|
132
|
+
</p>
|
|
133
|
+
<% end %>
|
|
134
|
+
</div>
|
|
135
|
+
<% end %>
|
|
136
|
+
|
|
137
|
+
<!-- Schema Content -->
|
|
138
|
+
<div class="card shadow-sm">
|
|
139
|
+
<div class="card-header bg-light">
|
|
140
|
+
<h5 class="card-title mb-0">
|
|
141
|
+
<i class="bi bi-code-square"></i>
|
|
142
|
+
Schema Content
|
|
143
|
+
<% if @schema_version.multi_file? %>
|
|
144
|
+
<small class="text-muted">(combined from all files)</small>
|
|
145
|
+
<% end %>
|
|
146
|
+
</h5>
|
|
147
|
+
</div>
|
|
148
|
+
<div class="card-body p-0">
|
|
149
|
+
<% if @schema_version.respond_to?(:content) && @schema_version.content.present? %>
|
|
150
|
+
<pre class="code-block m-0" id="schema-content"><%= @schema_version.content %></pre>
|
|
151
|
+
<% else %>
|
|
152
|
+
<div class="alert alert-warning m-3">
|
|
153
|
+
<i class="bi bi-exclamation-triangle"></i>
|
|
154
|
+
<strong>File too large to display</strong>
|
|
155
|
+
<p class="mb-0 mt-2">
|
|
156
|
+
This schema file is too large to display in the browser (limit: 200 KB).
|
|
157
|
+
Please use the "Download" button above to download and view it locally.
|
|
158
|
+
</p>
|
|
159
|
+
</div>
|
|
160
|
+
<% end %>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
<script>
|
|
167
|
+
function copyToClipboard() {
|
|
168
|
+
const content = document.getElementById('schema-content').textContent;
|
|
169
|
+
navigator.clipboard.writeText(content).then(function() {
|
|
170
|
+
// Show success feedback
|
|
171
|
+
const btn = event.target.closest('button');
|
|
172
|
+
const originalHTML = btn.innerHTML;
|
|
173
|
+
btn.innerHTML = '<i class="bi bi-check-lg"></i> Copied!';
|
|
174
|
+
btn.classList.remove('btn-outline-secondary');
|
|
175
|
+
btn.classList.add('btn-success');
|
|
176
|
+
|
|
177
|
+
setTimeout(function() {
|
|
178
|
+
btn.innerHTML = originalHTML;
|
|
179
|
+
btn.classList.remove('btn-success');
|
|
180
|
+
btn.classList.add('btn-outline-secondary');
|
|
181
|
+
}, 2000);
|
|
182
|
+
}, function(err) {
|
|
183
|
+
alert('Failed to copy to clipboard: ' + err);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
</script>
|